feat: migrate backend storage to postgres
Some checks failed
Deploy to Production / Build and Test (push) Successful in 10m51s
Deploy to Production / Deploy to Server (push) Failing after 6m41s

This commit is contained in:
你的用户名
2025-11-06 22:01:50 +08:00
parent 3646405a47
commit b68511b2e2
28 changed files with 2641 additions and 1801 deletions

View File

@@ -1,3 +1,4 @@
import { query } from './db';
import {
MOCK_ACCOUNTS,
MOCK_BUDGETS,
@@ -5,37 +6,87 @@ import {
MOCK_CURRENCIES,
MOCK_EXCHANGE_RATES,
} from './mock-data';
import db from './sqlite';
export function listAccounts() {
return MOCK_ACCOUNTS;
interface AccountRow {
id: number;
name: string;
type: string;
currency: string;
icon: null | string;
color: null | string;
user_id: null | number;
is_active: boolean;
}
export function listCategories() {
// 从数据库读取分类
interface CategoryRow {
id: number;
name: string;
type: string;
icon: null | string;
color: null | string;
user_id: null | number;
is_active: boolean;
}
function mapAccount(row: AccountRow) {
return {
id: row.id,
userId: row.user_id ?? 1,
name: row.name,
type: row.type,
currency: row.currency,
balance: 0,
icon: row.icon ?? '💳',
color: row.color ?? '#1677ff',
isActive: Boolean(row.is_active),
};
}
function mapCategory(row: CategoryRow) {
return {
id: row.id,
userId: row.user_id ?? 1,
name: row.name,
type: row.type as 'expense' | 'income',
icon: row.icon ?? '📝',
color: row.color ?? '#dfe4ea',
sortOrder: row.id,
isSystem: row.user_id === null,
isActive: Boolean(row.is_active),
};
}
export async function listAccounts() {
try {
const stmt = db.prepare(`
SELECT id, name, type, icon, color, user_id as userId, is_active as isActive
FROM finance_categories
WHERE is_active = 1
ORDER BY type, id
`);
const categories = stmt.all() as any[];
// 转换为前端需要的格式
return categories.map(cat => ({
id: cat.id,
userId: cat.userId,
name: cat.name,
type: cat.type,
icon: cat.icon,
color: cat.color,
sortOrder: cat.id,
isSystem: true,
isActive: Boolean(cat.isActive),
}));
const { rows } = await query<AccountRow>(
`SELECT id, name, type, currency, icon, color, user_id, is_active
FROM finance_accounts
ORDER BY id`,
);
if (rows.length === 0) {
return MOCK_ACCOUNTS;
}
return rows.map((row) => mapAccount(row));
} catch (error) {
console.error('从数据库读取分类失败使用MOCK数据:', error);
console.error('从数据库读取账户失败,使用 MOCK 数据:', error);
return MOCK_ACCOUNTS;
}
}
export async function listCategories() {
try {
const { rows } = await query<CategoryRow>(
`SELECT id, name, type, icon, color, user_id, is_active
FROM finance_categories
WHERE is_active = TRUE
ORDER BY type, id`,
);
if (rows.length === 0) {
return MOCK_CATEGORIES;
}
return rows.map((row) => mapCategory(row));
} catch (error) {
console.error('从数据库读取分类失败,使用 MOCK 数据:', error);
return MOCK_CATEGORIES;
}
}
@@ -52,76 +103,80 @@ export function listExchangeRates() {
return MOCK_EXCHANGE_RATES;
}
export function createCategoryRecord(category: any) {
export async function createCategoryRecord(category: any) {
try {
const stmt = db.prepare(`
INSERT INTO finance_categories (name, type, icon, color, user_id, is_active)
VALUES (?, ?, ?, ?, ?, 1)
`);
const result = stmt.run(
category.name,
category.type,
category.icon || '📝',
category.color || '#dfe4ea',
category.userId || 1
const { rows } = await query<CategoryRow>(
`INSERT INTO finance_categories (name, type, icon, color, user_id, is_active)
VALUES ($1, $2, $3, $4, $5, TRUE)
RETURNING id, name, type, icon, color, user_id, is_active`,
[
category.name,
category.type,
category.icon || '📝',
category.color || '#dfe4ea',
category.userId || 1,
],
);
return {
id: result.lastInsertRowid,
...category,
createdAt: new Date().toISOString(),
};
const row = rows[0];
return row
? {
...mapCategory(row),
createdAt: new Date().toISOString(),
}
: null;
} catch (error) {
console.error('创建分类失败:', error);
return null;
}
}
export function updateCategoryRecord(id: number, category: any) {
export async function updateCategoryRecord(id: number, category: any) {
try {
const updates: string[] = [];
const params: any[] = [];
if (category.name) {
updates.push('name = ?');
params.push(category.name);
updates.push(`name = $${params.length}`);
}
if (category.icon) {
updates.push('icon = ?');
params.push(category.icon);
updates.push(`icon = $${params.length}`);
}
if (category.color) {
updates.push('color = ?');
params.push(category.color);
updates.push(`color = $${params.length}`);
}
if (updates.length === 0) return null;
if (updates.length === 0) {
return null;
}
params.push(id);
const stmt = db.prepare(`
UPDATE finance_categories
SET ${updates.join(', ')}
WHERE id = ?
`);
stmt.run(...params);
// 返回更新后的分类
const selectStmt = db.prepare('SELECT * FROM finance_categories WHERE id = ?');
return selectStmt.get(id);
const setClause = updates.join(', ');
const { rows } = await query<CategoryRow>(
`UPDATE finance_categories
SET ${setClause}
WHERE id = $${params.length}
RETURNING id, name, type, icon, color, user_id, is_active`,
params,
);
const row = rows[0];
return row ? mapCategory(row) : null;
} catch (error) {
console.error('更新分类失败:', error);
return null;
}
}
export function deleteCategoryRecord(id: number) {
export async function deleteCategoryRecord(id: number) {
try {
// 软删除
const stmt = db.prepare(`
UPDATE finance_categories
SET is_active = 0
WHERE id = ?
`);
stmt.run(id);
await query(
`UPDATE finance_categories
SET is_active = FALSE
WHERE id = $1`,
[id],
);
return true;
} catch (error) {
console.error('删除分类失败:', error);