Files
discord-rpg-concept/shared/modules/game-settings/game-settings.service.ts

193 lines
5.7 KiB
TypeScript

import { eq } from "drizzle-orm";
import { gameSettings } from "@db/schema";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import type {
LevelingConfig,
EconomyConfig,
InventoryConfig,
LootdropConfig,
TriviaConfig,
ModerationConfig,
} from "@db/schema/game-settings";
export type GameSettingsData = {
leveling: LevelingConfig;
economy: EconomyConfig;
inventory: InventoryConfig;
lootdrop: LootdropConfig;
trivia: TriviaConfig;
moderation: ModerationConfig;
commands: Record<string, boolean>;
system: Record<string, unknown>;
};
let cachedSettings: GameSettingsData | null = null;
let cacheTimestamp = 0;
const CACHE_TTL_MS = 30000;
export const gameSettingsService = {
getSettings: async (useCache = true): Promise<GameSettingsData | null> => {
if (useCache && cachedSettings && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
return cachedSettings;
}
const settings = await DrizzleClient.query.gameSettings.findFirst({
where: eq(gameSettings.id, "default"),
});
if (!settings) return null;
cachedSettings = {
leveling: settings.leveling,
economy: settings.economy,
inventory: settings.inventory,
lootdrop: settings.lootdrop,
trivia: settings.trivia,
moderation: settings.moderation,
commands: settings.commands ?? {},
system: settings.system ?? {},
};
cacheTimestamp = Date.now();
return cachedSettings;
},
upsertSettings: async (data: Partial<GameSettingsData>) => {
const existing = await gameSettingsService.getSettings(false);
const values: typeof gameSettings.$inferInsert = {
id: "default",
leveling: data.leveling ?? existing?.leveling ?? gameSettingsService.getDefaultLeveling(),
economy: data.economy ?? existing?.economy ?? gameSettingsService.getDefaultEconomy(),
inventory: data.inventory ?? existing?.inventory ?? gameSettingsService.getDefaultInventory(),
lootdrop: data.lootdrop ?? existing?.lootdrop ?? gameSettingsService.getDefaultLootdrop(),
trivia: data.trivia ?? existing?.trivia ?? gameSettingsService.getDefaultTrivia(),
moderation: data.moderation ?? existing?.moderation ?? gameSettingsService.getDefaultModeration(),
commands: data.commands ?? existing?.commands ?? {},
system: data.system ?? existing?.system ?? {},
updatedAt: new Date(),
};
const [result] = await DrizzleClient.insert(gameSettings)
.values(values)
.onConflictDoUpdate({
target: gameSettings.id,
set: values,
})
.returning();
gameSettingsService.invalidateCache();
return result;
},
updateSection: async <K extends keyof GameSettingsData>(
section: K,
value: GameSettingsData[K]
) => {
const existing = await gameSettingsService.getSettings(false);
if (!existing) {
throw new Error("Game settings not found. Initialize settings first.");
}
const updates: Partial<GameSettingsData> = { [section]: value };
await gameSettingsService.upsertSettings(updates);
gameSettingsService.invalidateCache();
},
toggleCommand: async (commandName: string, enabled: boolean) => {
const settings = await gameSettingsService.getSettings(false);
if (!settings) {
throw new Error("Game settings not found. Initialize settings first.");
}
const commands = {
...settings.commands,
[commandName]: enabled,
};
await gameSettingsService.updateSection("commands", commands);
},
invalidateCache: () => {
cachedSettings = null;
cacheTimestamp = 0;
},
getDefaultLeveling: (): LevelingConfig => ({
base: 100,
exponent: 1.5,
chat: {
cooldownMs: 60000,
minXp: 5,
maxXp: 15,
},
}),
getDefaultEconomy: (): EconomyConfig => ({
daily: {
amount: "100",
streakBonus: "10",
weeklyBonus: "50",
cooldownMs: 86400000,
},
transfers: {
allowSelfTransfer: false,
minAmount: "1",
},
exam: {
multMin: 1.0,
multMax: 2.0,
},
}),
getDefaultInventory: (): InventoryConfig => ({
maxStackSize: "99",
maxSlots: 20,
}),
getDefaultLootdrop: (): LootdropConfig => ({
activityWindowMs: 300000,
minMessages: 5,
spawnChance: 0.1,
cooldownMs: 60000,
reward: {
min: 10,
max: 50,
currency: "AU",
},
}),
getDefaultTrivia: (): TriviaConfig => ({
entryFee: "50",
rewardMultiplier: 1.8,
timeoutSeconds: 30,
cooldownMs: 60000,
categories: [],
difficulty: "random",
}),
getDefaultModeration: (): ModerationConfig => ({
prune: {
maxAmount: 100,
confirmThreshold: 50,
batchSize: 100,
batchDelayMs: 1000,
},
}),
getDefaults: (): GameSettingsData => ({
leveling: gameSettingsService.getDefaultLeveling(),
economy: gameSettingsService.getDefaultEconomy(),
inventory: gameSettingsService.getDefaultInventory(),
lootdrop: gameSettingsService.getDefaultLootdrop(),
trivia: gameSettingsService.getDefaultTrivia(),
moderation: gameSettingsService.getDefaultModeration(),
commands: {},
system: {},
}),
};