Feature: Implemented Dynamic Admin Settings for Platform Addresses and Security Guidelines for Private Keys
This commit is contained in:
@@ -1,138 +1,188 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Globe,
|
Zap,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Bell,
|
Save,
|
||||||
Trash2,
|
RefreshCcw,
|
||||||
Smartphone,
|
Wallet,
|
||||||
Monitor,
|
Settings,
|
||||||
|
Server,
|
||||||
|
Key,
|
||||||
|
AlertCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function AdminSettingsPage() {
|
||||||
|
const [settings, setSettings] = useState({
|
||||||
|
sol_platform_address: '',
|
||||||
|
evm_platform_address: ''
|
||||||
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [message, setMessage] = useState({ type: '', text: '' });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSettings();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchSettings = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/settings');
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.error) throw new Error(data.error);
|
||||||
|
setSettings({
|
||||||
|
sol_platform_address: data.sol_platform_address || '',
|
||||||
|
evm_platform_address: data.evm_platform_address || ''
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
setMessage({ type: 'error', text: 'Ayarlar yüklenemedi: ' + err.message });
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSaving(true);
|
||||||
|
setMessage({ type: '', text: '' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/settings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(settings)
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.error) throw new Error(data.error);
|
||||||
|
setMessage({ type: 'success', text: 'Ayarlar başarıyla güncellendi.' });
|
||||||
|
} catch (err: any) {
|
||||||
|
setMessage({ type: 'error', text: 'Kaydetme hatası: ' + err.message });
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
<div className="max-w-4xl mx-auto space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-black text-gray-900 tracking-tight">Ayarlar</h1>
|
<h1 className="text-3xl font-black text-gray-900 tracking-tight italic">Platform Ayarları</h1>
|
||||||
<p className="text-sm text-gray-400 font-bold uppercase tracking-widest mt-2">Platform tercihlerinizi yönetin</p>
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-2 px-1">Sistem Genel Yapılandırması</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 px-6 py-2 bg-blue-50 rounded-full border border-blue-100">
|
||||||
|
<ShieldCheck size={14} className="text-blue-600" />
|
||||||
|
<span className="text-[10px] font-black text-blue-600 uppercase tracking-widest">Global Kontrol Ünitesi</span>
|
||||||
</div>
|
</div>
|
||||||
<button className="px-8 py-4 bg-gray-900 text-white rounded-2xl font-black text-sm shadow-xl shadow-gray-200 hover:bg-gray-800 transition active:scale-95">
|
|
||||||
Değişiklikleri Kaydet
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-10">
|
{/* Main Form */}
|
||||||
{/* Left Column: Sections */}
|
<form onSubmit={handleSave} className="space-y-8">
|
||||||
<div className="lg:col-span-2 space-y-8">
|
{/* Platform Addresses Card */}
|
||||||
{/* General Section */}
|
<div className="bg-white p-10 rounded-[48px] border border-gray-100 shadow-sm space-y-10">
|
||||||
<section className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm space-y-8">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-4 border-b border-gray-50 pb-6">
|
<div className="w-12 h-12 bg-gray-50 rounded-2xl flex items-center justify-center text-gray-900 group-hover:bg-blue-600 group-hover:text-white transition-all">
|
||||||
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600">
|
<Wallet size={24} />
|
||||||
<Globe size={24} />
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-black text-gray-900">Merkezi Cüzdanlar (Platform)</h3>
|
||||||
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-1">Süpürülen tüm fonların toplanacağı ana adresler</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-8">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<label className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] ml-1">Solana Platform Adresi</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Server className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-300" size={20} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={settings.sol_platform_address}
|
||||||
|
onChange={(e) => setSettings({ ...settings, sol_platform_address: e.target.value })}
|
||||||
|
placeholder="Solana Wallet Address"
|
||||||
|
className="w-full pl-14 pr-6 py-5 bg-gray-50 border-2 border-transparent focus:border-purple-500 focus:bg-white rounded-[24px] outline-none transition-all font-bold text-gray-900 font-mono text-sm"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tight">Genel Ayarlar</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="space-y-3">
|
||||||
<div className="space-y-3">
|
<label className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] ml-1">EVM Platform Adresi (Polygon/BSC/ETH)</label>
|
||||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Mağaza Adı</label>
|
<div className="relative">
|
||||||
<input type="text" defaultValue="P2CGateway" className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none" />
|
<Server className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-300" size={20} />
|
||||||
</div>
|
<input
|
||||||
<div className="space-y-3">
|
type="text"
|
||||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Destek E-postası</label>
|
required
|
||||||
<input type="email" defaultValue="support@ayris.dev" className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none" />
|
value={settings.evm_platform_address}
|
||||||
</div>
|
onChange={(e) => setSettings({ ...settings, evm_platform_address: e.target.value })}
|
||||||
<div className="space-y-3">
|
placeholder="0x..."
|
||||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Para Birimi</label>
|
className="w-full pl-14 pr-6 py-5 bg-gray-50 border-2 border-transparent focus:border-blue-500 focus:bg-white rounded-[24px] outline-none transition-all font-bold text-gray-900 font-mono text-sm"
|
||||||
<select className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none appearance-none">
|
/>
|
||||||
<option>Türk Lirası (₺)</option>
|
|
||||||
<option>US Dollar ($)</option>
|
|
||||||
<option>Euro (€)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Zaman Dilimi</label>
|
|
||||||
<select className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none appearance-none">
|
|
||||||
<option>Istanbul (GMT+3)</option>
|
|
||||||
<option>London (GMT+0)</option>
|
|
||||||
<option>New York (EST)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* Security Section */}
|
<div className="p-6 bg-blue-50 rounded-3xl border border-blue-100 flex gap-4 items-start">
|
||||||
<section className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm space-y-8">
|
<AlertCircle className="text-blue-600 mt-1 shrink-0" size={20} />
|
||||||
<div className="flex items-center gap-4 border-b border-gray-50 pb-6">
|
<p className="text-xs text-blue-600 font-medium leading-relaxed">
|
||||||
<div className="w-12 h-12 bg-emerald-50 rounded-2xl flex items-center justify-center text-emerald-600">
|
Bu adresler, merchant'ların yaptığı tahsilatların (komisyonunuz kesildikten sonra kalan kısmın değil, platforma ait ana kesintinin veya tüm fonların) toplanacağı ana havuzunuzdur. Lütfen adreslerin doğruluğunu iki kez kontrol edin.
|
||||||
<ShieldCheck size={24} />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tight">Güvenlik</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between p-6 bg-gray-50 rounded-[32px]">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-sm font-black text-gray-900">İki Faktörlü Doğrulama</p>
|
|
||||||
<p className="text-[10px] text-gray-400 font-bold uppercase">Hesabınıza ekstra bir güvenlik katmanı ekleyin</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-12 h-6 bg-blue-500 rounded-full relative cursor-pointer">
|
|
||||||
<div className="absolute right-1 top-1 w-4 h-4 bg-white rounded-full"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-6 bg-gray-50 rounded-[32px]">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-sm font-black text-gray-900">API Erişimi</p>
|
|
||||||
<p className="text-[10px] text-gray-400 font-bold uppercase">Harici uygulamalar için anahtar yönetimi</p>
|
|
||||||
</div>
|
|
||||||
<button className="text-blue-600 text-[10px] font-black uppercase tracking-widest hover:underline">Anahtarları Düzenle</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column: Notifications & Danger Zone */}
|
|
||||||
<div className="space-y-8">
|
|
||||||
<section className="bg-white p-8 rounded-[40px] border border-gray-100 shadow-sm space-y-6">
|
|
||||||
<div className="flex items-center gap-4 mb-2">
|
|
||||||
<div className="w-10 h-10 bg-orange-50 rounded-xl flex items-center justify-center text-orange-600">
|
|
||||||
<Bell size={20} />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-lg font-black text-gray-900 uppercase tracking-tight">Bildirimler</h2>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{['Yeni Satışlar', 'Müşteri Mesajları', 'Sistem Güncellemeleri'].map((item) => (
|
|
||||||
<label key={item} className="flex items-center gap-4 cursor-pointer group">
|
|
||||||
<input type="checkbox" defaultChecked className="w-5 h-5 rounded-lg border-2 border-gray-100 text-blue-600 focus:ring-blue-500" />
|
|
||||||
<span className="text-sm font-bold text-gray-500 group-hover:text-gray-900 transition">{item}</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="bg-red-50 p-8 rounded-[40px] border border-red-100 space-y-6">
|
|
||||||
<div className="flex items-center gap-4 text-red-600">
|
|
||||||
<Trash2 size={20} />
|
|
||||||
<h2 className="text-lg font-black uppercase tracking-tight">Tehlikeli Bölge</h2>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs font-bold text-red-400 leading-relaxed uppercase tracking-wider">
|
|
||||||
Mağaza verilerini kalıcı olarak silmek veya hesabı kapatmak için bu bölümü kullanın. Bu işlem geri alınamaz.
|
|
||||||
</p>
|
</p>
|
||||||
<button className="w-full py-4 bg-red-600 text-white rounded-2xl font-black text-sm hover:bg-red-700 transition shadow-lg shadow-red-100">
|
</div>
|
||||||
Mağazayı Devre Dışı Bırak
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer Meta */}
|
{/* Info Card: Gas Tank Secrets */}
|
||||||
<div className="pt-12 text-center">
|
<div className="bg-gray-900 p-10 rounded-[48px] shadow-2xl space-y-8 relative overflow-hidden group">
|
||||||
<p className="text-[10px] text-gray-300 font-black uppercase tracking-[0.4em]">v1.0.4 Platinum Enterprise Edition</p>
|
<div className="absolute top-0 right-0 p-10 opacity-5 group-hover:scale-110 transition-transform">
|
||||||
</div>
|
<Key size={120} className="text-white" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 relative">
|
||||||
|
<div className="w-12 h-12 bg-white/10 rounded-2xl flex items-center justify-center text-white">
|
||||||
|
<Zap size={24} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-black text-white italic">Kritik Güvenlik: Gas Tank Private Key</h3>
|
||||||
|
<p className="text-gray-400 font-medium leading-relaxed max-w-2xl">
|
||||||
|
Süpürme (Sweep) işlemlerinin blokzinciri ücretlerini ödeyen "Gaz Tankı" cüzdanınızın Private Key'i, en yüksek güvenlik için veritabanında değil, sunucu tarafında <strong>Coolify Environment Variables</strong> (`CRYPTO_GAS_TANK_KEY`) olarak saklanmalıdır.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4 p-6 bg-white/5 rounded-3xl border border-white/10 items-center">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-emerald-500"></div>
|
||||||
|
<span className="text-[10px] font-black text-gray-500 uppercase tracking-widest">Coolify Panelden Yönetildi</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status and Action */}
|
||||||
|
{message.text && (
|
||||||
|
<div className={`p-6 rounded-[30px] border flex items-center gap-4 animate-in slide-in-from-top-2 duration-300 ${
|
||||||
|
message.type === 'success' ? 'bg-emerald-50 border-emerald-100 text-emerald-700' : 'bg-red-50 border-red-100 text-red-700'
|
||||||
|
}`}>
|
||||||
|
{message.type === 'success' ? <ShieldCheck size={20} /> : <AlertCircle size={20} />}
|
||||||
|
<span className="text-xs font-black uppercase tracking-widest italic">{message.text}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-6 pt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={fetchSettings}
|
||||||
|
className="p-5 text-gray-400 hover:text-gray-900 transition-colors"
|
||||||
|
title="Yenile"
|
||||||
|
>
|
||||||
|
<RefreshCcw size={24} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSaving || isLoading}
|
||||||
|
className="px-14 py-6 bg-blue-600 text-white rounded-[28px] font-black text-sm uppercase tracking-[0.2em] hover:bg-blue-700 transition shadow-2xl shadow-blue-200 disabled:opacity-50 flex items-center gap-3 italic"
|
||||||
|
>
|
||||||
|
{isSaving ? 'Kaydediliyor...' : 'Konfigurasyonu Güncelle'}
|
||||||
|
<Save size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
54
app/api/admin/settings/route.ts
Normal file
54
app/api/admin/settings/route.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
// Ensure table exists (Safe initialization)
|
||||||
|
await db.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS system_settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result = await db.query('SELECT * FROM system_settings');
|
||||||
|
|
||||||
|
// Convert to key-value object
|
||||||
|
const settings: Record<string, string> = {};
|
||||||
|
result.rows.forEach(row => {
|
||||||
|
settings[row.key] = row.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fill defaults if empty
|
||||||
|
if (!settings.sol_platform_address) settings.sol_platform_address = process.env.SOL_PLATFORM_ADDRESS || '';
|
||||||
|
if (!settings.evm_platform_address) settings.evm_platform_address = process.env.EVM_PLATFORM_ADDRESS || '';
|
||||||
|
|
||||||
|
return NextResponse.json(settings);
|
||||||
|
} catch (err: any) {
|
||||||
|
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await req.json();
|
||||||
|
const { sol_platform_address, evm_platform_address } = body;
|
||||||
|
|
||||||
|
const queries = [
|
||||||
|
{ key: 'sol_platform_address', value: sol_platform_address },
|
||||||
|
{ key: 'evm_platform_address', value: evm_platform_address }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const q of queries) {
|
||||||
|
await db.query(
|
||||||
|
'INSERT INTO system_settings (key, value, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()',
|
||||||
|
[q.key, q.value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (err: any) {
|
||||||
|
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,10 +45,18 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({ success: false, error: `No temporary wallet found for ${walletType}` }, { status: 500 });
|
return NextResponse.json({ success: false, error: `No temporary wallet found for ${walletType}` }, { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Define Platform Address (In production, load from env/settings)
|
// 3. Define Platform Address (Fetch from dynamic settings)
|
||||||
const platformAddress = selectedNetwork === 'SOLANA'
|
const platformAddresses = await (async () => {
|
||||||
? process.env.SOL_PLATFORM_ADDRESS || "5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe"
|
const result = await db.query('SELECT key, value FROM system_settings WHERE key IN (\'sol_platform_address\', \'evm_platform_address\')');
|
||||||
: process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
|
const map: Record<string, string> = {};
|
||||||
|
result.rows.forEach(r => map[r.key] = r.value);
|
||||||
|
return {
|
||||||
|
sol: map.sol_platform_address || process.env.SOL_PLATFORM_ADDRESS || "5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe",
|
||||||
|
evm: map.evm_platform_address || process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const platformAddress = selectedNetwork === 'SOLANA' ? platformAddresses.sol : platformAddresses.evm;
|
||||||
|
|
||||||
// 4. Define Merchant Address (Fetch from transaction's merchant)
|
// 4. Define Merchant Address (Fetch from transaction's merchant)
|
||||||
const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [transaction.merchant_id]);
|
const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [transaction.merchant_id]);
|
||||||
|
|||||||
19
lib/settings.ts
Normal file
19
lib/settings.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { db } from './db';
|
||||||
|
|
||||||
|
export async function getSystemSetting(key: string, defaultValue: string = ''): Promise<string> {
|
||||||
|
try {
|
||||||
|
const result = await db.query('SELECT value FROM system_settings WHERE key = $1', [key]);
|
||||||
|
if (result.rows.length > 0) {
|
||||||
|
return result.rows[0].value;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[Settings] Failed to fetch key ${key}:`, err);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPlatformAddresses() {
|
||||||
|
const sol = await getSystemSetting('sol_platform_address', process.env.SOL_PLATFORM_ADDRESS || '');
|
||||||
|
const evm = await getSystemSetting('evm_platform_address', process.env.EVM_PLATFORM_ADDRESS || '');
|
||||||
|
return { sol, evm };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user