Files
fluxent-web/lib/actions/account.ts
T

218 lines
6.9 KiB
TypeScript

"use server";
import { z } from "zod";
import { and, desc, eq, isNull } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { accounts, channels, type Account } from "@/lib/db/schema";
async function requireUser() {
const session = await auth();
if (!session?.user?.id) {
throw new Error("请先登录");
}
return session.user.id;
}
const accountSchema = z.object({
name: z.string().min(1, "请输入账户名称").max(100),
accountType: z.enum(["BANK", "E_WALLET", "CASH"] as const),
balanceType: z.enum(["ASSET", "LIABILITY", "EQUITY"] as const),
primaryCurrency: z.string().trim().max(10).optional().nullable(),
supportedCurrencies: z.array(z.string().trim()).optional().nullable(),
remark: z.string().max(500).optional().nullable(),
ext: z.string().optional().nullable(),
// 银行账户字段
issuerName: z.string().max(100).optional().nullable(),
accountNumber: z.string().max(100).optional().nullable(),
// 电子钱包字段
platform: z.string().max(64).optional().nullable(),
accountId: z.string().max(100).optional().nullable(),
// 现金账户字段
location: z.string().max(150).optional().nullable(),
});
export type AccountInput = z.infer<typeof accountSchema>;
export type AccountWithChannels = Account & {
channelCount: number;
};
export async function getAccountsAction(): Promise<{
success: boolean;
data?: AccountWithChannels[];
error?: string;
}> {
try {
const userId = await requireUser();
const userAccounts = await db
.select()
.from(accounts)
.where(and(eq(accounts.userId, userId), isNull(accounts.deletedAt)))
.orderBy(desc(accounts.createdAt));
const userChannels = await db
.select({ id: channels.id, refAccounts: channels.refAccounts })
.from(channels)
.where(and(eq(channels.userId, userId), isNull(channels.deletedAt)));
const result: AccountWithChannels[] = userAccounts.map((acc) => {
const count = userChannels.filter((ch) =>
Array.isArray(ch.refAccounts) && ch.refAccounts.includes(acc.id)
).length;
return {
...acc,
channelCount: count,
};
});
return { success: true, data: result };
} catch (err) {
console.error("getAccountsAction error:", err);
return { success: false, error: err instanceof Error ? err.message : "获取账户失败" };
}
}
export async function createAccountAction(
data: AccountInput
): Promise<{ success: boolean; data?: Account; error?: string }> {
try {
const userId = await requireUser();
const parsed = accountSchema.safeParse(data);
if (!parsed.success) {
return { success: false, error: parsed.error.issues[0]?.message || "参数错误" };
}
const val = parsed.data;
const [newAccount] = await db
.insert(accounts)
.values({
userId,
name: val.name.trim(),
accountType: val.accountType,
balanceType: val.balanceType,
primaryCurrency: val.primaryCurrency ? val.primaryCurrency.toUpperCase() : null,
supportedCurrencies: val.supportedCurrencies && val.supportedCurrencies.length > 0 ? val.supportedCurrencies : null,
remark: val.remark?.trim() || null,
ext: val.ext?.trim() || null,
issuerName: val.issuerName?.trim() || null,
accountNumber: val.accountNumber?.trim() || null,
platform: val.platform?.trim() || null,
accountId: val.accountId?.trim() || null,
location: val.location?.trim() || null,
isActive: true,
})
.returning();
revalidatePath("/accounts");
revalidatePath("/channels");
revalidatePath("/");
return { success: true, data: newAccount };
} catch (err) {
console.error("createAccountAction error:", err);
return { success: false, error: err instanceof Error ? err.message : "创建账户失败" };
}
}
export async function updateAccountAction(
id: string,
data: AccountInput
): Promise<{ success: boolean; data?: Account; error?: string }> {
try {
const userId = await requireUser();
const parsed = accountSchema.safeParse(data);
if (!parsed.success) {
return { success: false, error: parsed.error.issues[0]?.message || "参数错误" };
}
const val = parsed.data;
const [updated] = await db
.update(accounts)
.set({
name: val.name.trim(),
accountType: val.accountType,
balanceType: val.balanceType,
primaryCurrency: val.primaryCurrency ? val.primaryCurrency.toUpperCase() : null,
supportedCurrencies: val.supportedCurrencies && val.supportedCurrencies.length > 0 ? val.supportedCurrencies : null,
remark: val.remark?.trim() || null,
ext: val.ext?.trim() || null,
issuerName: val.issuerName?.trim() || null,
accountNumber: val.accountNumber?.trim() || null,
platform: val.platform?.trim() || null,
accountId: val.accountId?.trim() || null,
location: val.location?.trim() || null,
updatedAt: new Date(),
})
.where(and(eq(accounts.id, id), eq(accounts.userId, userId), isNull(accounts.deletedAt)))
.returning();
if (!updated) {
return { success: false, error: "未找到该账户或无权修改" };
}
revalidatePath("/accounts");
revalidatePath("/channels");
revalidatePath("/");
return { success: true, data: updated };
} catch (err) {
console.error("updateAccountAction error:", err);
return { success: false, error: err instanceof Error ? err.message : "更新账户失败" };
}
}
export async function toggleAccountActiveAction(
id: string,
isActive: boolean
): Promise<{ success: boolean; error?: string }> {
try {
const userId = await requireUser();
await db
.update(accounts)
.set({
isActive,
updatedAt: new Date(),
})
.where(and(eq(accounts.id, id), eq(accounts.userId, userId), isNull(accounts.deletedAt)));
revalidatePath("/accounts");
return { success: true };
} catch (err) {
console.error("toggleAccountActiveAction error:", err);
return { success: false, error: err instanceof Error ? err.message : "状态切换失败" };
}
}
export async function deleteAccountAction(
id: string
): Promise<{ success: boolean; error?: string }> {
try {
const userId = await requireUser();
// 软删除
const [deleted] = await db
.update(accounts)
.set({
deletedAt: new Date(),
updatedAt: new Date(),
})
.where(and(eq(accounts.id, id), eq(accounts.userId, userId), isNull(accounts.deletedAt)))
.returning();
if (!deleted) {
return { success: false, error: "账户不存在或已删除" };
}
revalidatePath("/accounts");
revalidatePath("/channels");
revalidatePath("/");
return { success: true };
} catch (err) {
console.error("deleteAccountAction error:", err);
return { success: false, error: err instanceof Error ? err.message : "删除账户失败" };
}
}