226 lines
5.6 KiB
TypeScript
226 lines
5.6 KiB
TypeScript
/**
|
||
* 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",
|
||
});
|
||
}
|
||
}
|