nac-presale/server/configDb.ts

226 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Presale Configuration Database Helpers
*
* Manages the presale_config table — a key-value store for
* admin-editable presale parameters.
*
* Default values are seeded on first access if not present.
*/
import { eq } from "drizzle-orm";
import { getDb } from "./db";
import { presaleConfig } from "../drizzle/schema";
// ─── Default configuration values ─────────────────────────────────────────────
export const DEFAULT_CONFIG: Array<{
key: string;
value: string;
label: string;
description: string;
type: string;
}> = [
{
key: "presaleEndDate",
value: "2026-06-30T23:59:59Z",
label: "预售结束时间 (Presale End Date)",
description: "ISO 8601 格式例如2026-06-30T23:59:59Z",
type: "date",
},
{
key: "tokenPrice",
value: "0.02",
label: "代币价格 (Token Price, USDT)",
description: "每枚 XIC 的 USDT 价格",
type: "number",
},
{
key: "hardCap",
value: "5000000",
label: "硬顶 (Hard Cap, USDT)",
description: "预售最大募资额USDT",
type: "number",
},
{
key: "listingPrice",
value: "0.10",
label: "上市目标价格 (Target Listing Price, USDT)",
description: "预计上市价格(仅展示用)",
type: "number",
},
{
key: "totalSupply",
value: "100000000000",
label: "总供应量 (Total Supply)",
description: "XIC 代币总供应量",
type: "number",
},
{
key: "maxPurchaseUsdt",
value: "50000",
label: "单笔最大购买额 (Max Purchase, USDT)",
description: "单笔购买最大 USDT 金额",
type: "number",
},
{
key: "presaleStatus",
value: "live",
label: "预售状态 (Presale Status)",
description: "live = 进行中paused = 暂停ended = 已结束",
type: "text",
},
{
key: "heroTitle",
value: "XIC Token Presale",
label: "首页标题 (Hero Title)",
description: "首页大标题文字",
type: "text",
},
{
key: "heroSubtitle",
value: "New AssetChain — The next-generation RWA native blockchain with AI-native compliance, CBPP consensus, and Charter smart contracts.",
label: "首页副标题 (Hero Subtitle)",
description: "首页副标题文字",
type: "text",
},
{
key: "tronReceivingAddress",
value: "TWc2ugYBFN5aSoimAh4qGt9oMyket6NYZp",
label: "TRC20 收款地址 (TRON Receiving Address)",
description: "接收 TRC20 USDT 的 TRON 地址",
type: "text",
},
{
key: "telegramBotToken",
value: "",
label: "Telegram Bot Token",
description: "从 @BotFather 获取的 Bot Token用于发送购买通知",
type: "text",
},
{
key: "telegramChatId",
value: "",
label: "Telegram Chat ID",
description: "接收通知的 Chat ID个人账号或群组",
type: "text",
},
];
export interface ConfigMap {
[key: string]: string;
}
/**
* Seed default config values if not already present.
*/
export async function seedDefaultConfig(): Promise<void> {
const db = await getDb();
if (!db) return;
for (const item of DEFAULT_CONFIG) {
try {
const existing = await db
.select()
.from(presaleConfig)
.where(eq(presaleConfig.key, item.key))
.limit(1);
if (existing.length === 0) {
await db.insert(presaleConfig).values({
key: item.key,
value: item.value,
label: item.label,
description: item.description,
type: item.type,
});
}
} catch (e) {
console.warn(`[Config] Failed to seed ${item.key}:`, e);
}
}
}
/**
* Get all config values as a key-value map.
* Falls back to DEFAULT_CONFIG values if DB is unavailable.
*/
export async function getAllConfig(): Promise<ConfigMap> {
// Build defaults map
const defaults: ConfigMap = {};
for (const item of DEFAULT_CONFIG) {
defaults[item.key] = item.value;
}
const db = await getDb();
if (!db) return defaults;
try {
const rows = await db.select().from(presaleConfig);
const result: ConfigMap = { ...defaults };
for (const row of rows) {
result[row.key] = row.value;
}
return result;
} catch (e) {
console.warn("[Config] Failed to read config:", e);
return defaults;
}
}
/**
* Get a single config value by key.
*/
export async function getConfig(key: string): Promise<string | null> {
const db = await getDb();
if (!db) {
const def = DEFAULT_CONFIG.find((c) => c.key === key);
return def?.value ?? null;
}
try {
const rows = await db
.select()
.from(presaleConfig)
.where(eq(presaleConfig.key, key))
.limit(1);
if (rows.length > 0) return rows[0].value;
// Fall back to default
const def = DEFAULT_CONFIG.find((c) => c.key === key);
return def?.value ?? null;
} catch (e) {
console.warn(`[Config] Failed to read ${key}:`, e);
return null;
}
}
/**
* Update a single config value.
*/
export async function setConfig(key: string, value: string): Promise<void> {
const db = await getDb();
if (!db) throw new Error("DB unavailable");
const existing = await db
.select()
.from(presaleConfig)
.where(eq(presaleConfig.key, key))
.limit(1);
if (existing.length > 0) {
await db
.update(presaleConfig)
.set({ value })
.where(eq(presaleConfig.key, key));
} else {
const def = DEFAULT_CONFIG.find((c) => c.key === key);
await db.insert(presaleConfig).values({
key,
value,
label: def?.label ?? key,
description: def?.description ?? "",
type: def?.type ?? "text",
});
}
}