feat: migrate backend storage to postgres
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user