feat: implement hybrid database architecture and frontend encryption

- Add PostgreSQL + SQLite hybrid database support with automatic switching
- Implement frontend AES-GCM + RSA-OAEP encryption for sensitive data
- Add comprehensive DatabaseInterface with all required methods
- Fix compilation issues with interface consistency
- Update all database method signatures to use DatabaseInterface
- Add missing UpdateTraderInitialBalance method to PostgreSQL implementation
- Integrate RSA public key distribution via /api/config endpoint
- Add frontend crypto service with proper error handling
- Support graceful degradation between encrypted and plaintext transmission
- Add directory creation for RSA keys and PEM parsing fixes
- Test both SQLite and PostgreSQL modes successfully
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
icy
2025-11-06 01:50:06 +08:00
parent ba0bb8c365
commit 7d58f56e49
104 changed files with 16864 additions and 4152 deletions

View File

@@ -0,0 +1,51 @@
import { useEffect, useState } from 'react'
interface UseCounterAnimationOptions {
start?: number
end: number
duration?: number
decimals?: number
}
export function useCounterAnimation({
start = 0,
end,
duration = 2000,
decimals = 0,
}: UseCounterAnimationOptions): number {
const [count, setCount] = useState(start)
useEffect(() => {
if (end === 0) return
let startTime: number | null = null
let animationFrame: number
const animate = (currentTime: number) => {
if (startTime === null) startTime = currentTime
const progress = Math.min((currentTime - startTime) / duration, 1)
// 使用 easeOutExpo 缓动函数,让数字快速启动后缓慢停止
const easeOutExpo = progress === 1 ? 1 : 1 - Math.pow(2, -10 * progress)
const currentCount = start + (end - start) * easeOutExpo
setCount(currentCount)
if (progress < 1) {
animationFrame = requestAnimationFrame(animate)
} else {
setCount(end)
}
}
animationFrame = requestAnimationFrame(animate)
return () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame)
}
}
}, [start, end, duration])
return decimals > 0 ? parseFloat(count.toFixed(decimals)) : Math.floor(count)
}

View File

@@ -0,0 +1,63 @@
import { useState, useEffect } from 'react'
interface GitHubStats {
stars: number
forks: number
createdAt: string
daysOld: number
isLoading: boolean
error: string | null
}
export function useGitHubStats(owner: string, repo: string): GitHubStats {
const [stats, setStats] = useState<GitHubStats>({
stars: 0,
forks: 0,
createdAt: '',
daysOld: 0,
isLoading: true,
error: null,
})
useEffect(() => {
const fetchGitHubStats = async () => {
try {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}`
)
if (!response.ok) {
throw new Error('Failed to fetch GitHub stats')
}
const data = await response.json()
// Calculate days since creation
const createdDate = new Date(data.created_at)
const now = new Date()
const diffTime = Math.abs(now.getTime() - createdDate.getTime())
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
setStats({
stars: data.stargazers_count,
forks: data.forks_count,
createdAt: data.created_at,
daysOld: diffDays,
isLoading: false,
error: null,
})
} catch (error) {
console.error('Error fetching GitHub stats:', error)
setStats((prev) => ({
...prev,
isLoading: false,
error: error instanceof Error ? error.message : 'Unknown error',
}))
}
}
fetchGitHubStats()
}, [owner, repo])
return stats
}

View File

@@ -1,29 +1,29 @@
import { useEffect, useState } from 'react';
import { getSystemConfig, type SystemConfig } from '../lib/config';
import { useEffect, useState } from 'react'
import { getSystemConfig, type SystemConfig } from '../lib/config'
export function useSystemConfig() {
const [config, setConfig] = useState<SystemConfig | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [config, setConfig] = useState<SystemConfig | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let mounted = true;
let mounted = true
getSystemConfig()
.then((data) => {
if (!mounted) return;
setConfig(data);
setLoading(false);
if (!mounted) return
setConfig(data)
setLoading(false)
})
.catch((err: Error) => {
if (!mounted) return;
console.error('Failed to fetch system config:', err);
setError(err.message);
setLoading(false);
});
if (!mounted) return
console.error('Failed to fetch system config:', err)
setError(err.message)
setLoading(false)
})
return () => {
mounted = false;
};
}, []);
mounted = false
}
}, [])
return { config, loading, error };
}
return { config, loading, error }
}