feat(db): 新增 exchange_rates 汇率表,支持联合主键与基础交叉汇率数据
This commit is contained in:
parent
8f17573fa4
commit
84b8dc3226
16
Memory.md
Normal file
16
Memory.md
Normal file
@ -0,0 +1,16 @@
|
||||
完成根目录的 Next.js 初始化、基础依赖安装与环境变量配置。
|
||||
|
||||
- 完成基于单例模式的数据库连接配置,并设定 Drizzle 迁移工具。
|
||||
- 修复网络连接,成功将 users 表推送至数据库。
|
||||
- 成功定義資產枚舉與 assets 表,支持跨資產標識。
|
||||
- 完成核心 transactions 表的建立,並嚴格運用了 numeric(36,18) 的高精度配置。
|
||||
- 完成高精度交易流水 (transactions) 的 Server Actions 开发,成功实现了字符串级别的高精度防腐层拦截。
|
||||
- 完成 shadcn/ui 初始化,集成 next-themes,并拉取核心组件库。
|
||||
- 完成 /dashboard 基础布局架构,接管根路由。
|
||||
- 引入 Zod 和 Big.js,完成资产表 (assets) 的 Server Actions 读写接口开发。
|
||||
- 完成 /dashboard/assets 页面,成功打通前后端资产录入数据流转。
|
||||
- 修复 /dashboard/transactions 404,完成高精度流水录入与展示功能。
|
||||
- 完成 UI 层的高精度数据格式化,实现不同资产类型的动态展示精度。
|
||||
- 完成持仓聚合计算逻辑,并构建了 Dashboard 首页持仓卡片矩阵。
|
||||
- 引入 recharts 图表引擎,完成 Dashboard 资产分布环形图的构建。
|
||||
- 汇率表已建立,支持跨币种(如 BTC->USD)的交叉汇率架构。
|
||||
24
scripts/seed-exchange.ts
Normal file
24
scripts/seed-exchange.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { db } from '@/db';
|
||||
import { exchangeRates } from '@/db/schema';
|
||||
|
||||
const seeds = [
|
||||
{ fromCurrency: 'USD', toCurrency: 'CNY', rate: '7.23' },
|
||||
{ fromCurrency: 'HKD', toCurrency: 'CNY', rate: '0.92' },
|
||||
{ fromCurrency: 'BTC', toCurrency: 'USD', rate: '65000' },
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
for (const s of seeds) {
|
||||
await db
|
||||
.insert(exchangeRates)
|
||||
.values(s)
|
||||
.onConflictDoUpdate({
|
||||
target: [exchangeRates.fromCurrency, exchangeRates.toCurrency],
|
||||
set: { rate: s.rate, updatedAt: new Date() },
|
||||
});
|
||||
console.log(`Seeded: ${s.fromCurrency} -> ${s.toCurrency} = ${s.rate}`);
|
||||
}
|
||||
console.log('Exchange rate seed complete.');
|
||||
}
|
||||
|
||||
seed().catch(console.error);
|
||||
68
src/actions/exchange.ts
Normal file
68
src/actions/exchange.ts
Normal file
@ -0,0 +1,68 @@
|
||||
'use server';
|
||||
|
||||
import { db } from '@/db';
|
||||
import { exchangeRates } from '@/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
|
||||
const updateExchangeRateSchema = z.object({
|
||||
from: z.string().min(1).max(10),
|
||||
to: z.string().min(1).max(10),
|
||||
rate: z.string().min(1),
|
||||
});
|
||||
|
||||
export async function updateExchangeRate(
|
||||
from: string,
|
||||
to: string,
|
||||
rate: string,
|
||||
) {
|
||||
const validation = updateExchangeRateSchema.safeParse({ from, to, rate });
|
||||
if (!validation.success) {
|
||||
return { success: false, error: 'Invalid input' };
|
||||
}
|
||||
|
||||
try {
|
||||
await db
|
||||
.insert(exchangeRates)
|
||||
.values({
|
||||
fromCurrency: validation.data.from.toUpperCase(),
|
||||
toCurrency: validation.data.to.toUpperCase(),
|
||||
rate: validation.data.rate,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [exchangeRates.fromCurrency, exchangeRates.toCurrency],
|
||||
set: {
|
||||
rate: validation.data.rate,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
'code' in error &&
|
||||
(error as { code: string }).code === '23505'
|
||||
) {
|
||||
return { success: false, error: 'Failed to update exchange rate' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getExchangeRate(from: string, to: string) {
|
||||
const result = await db
|
||||
.select()
|
||||
.from(exchangeRates)
|
||||
.where(
|
||||
eq(exchangeRates.fromCurrency, from.toUpperCase()),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return result[0] || null;
|
||||
}
|
||||
|
||||
export async function getAllExchangeRates() {
|
||||
return db.select().from(exchangeRates).execute();
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { pgTable, uuid, varchar, timestamp, pgEnum, numeric } from "drizzle-orm/pg-core";
|
||||
import { pgTable, uuid, varchar, timestamp, pgEnum, numeric, uniqueIndex } from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||
@ -51,3 +51,14 @@ export const transactions = pgTable("transactions", {
|
||||
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" })
|
||||
.defaultNow(),
|
||||
});
|
||||
|
||||
export const exchangeRates = pgTable("exchange_rates", {
|
||||
id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||
fromCurrency: varchar("from_currency", { length: 10 }).notNull(),
|
||||
toCurrency: varchar("to_currency", { length: 10 }).notNull(),
|
||||
rate: numeric("rate", { precision: 20, scale: 8 }).notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" })
|
||||
.defaultNow(),
|
||||
}, (table) => [
|
||||
uniqueIndex("currency_pair_idx").on(table.fromCurrency, table.toCurrency),
|
||||
]);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user