"use server"; import { z } from "zod"; import { and, desc, eq, inArray, isNull } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; import { accounts, channels, type Channel, } from "@/lib/db/schema"; async function requireUser() { const session = await auth(); if (!session?.user?.id) { throw new Error("请先登录"); } return session.user.id; } const channelSchema = z.object({ channelType: z.enum(["PAYMENT_CARD", "E_WALLET", "CASH", "TRANSFER"] as const), refAccounts: z.array(z.string().uuid("无效的账户标识")).min(1, "请至少关联一个资金账户"), desc: z.string().max(500).optional().nullable(), ext: z.string().optional().nullable(), // 支付卡特定字段 region: z.string().max(10).optional().nullable(), issuerName: z.string().max(100).optional().nullable(), cardType: z.enum(["CREDIT", "DEBIT"] as const).optional().nullable(), cardNumberFull: z.string().max(100).optional().nullable(), cardNumberSuffix: z.string().max(10).optional().nullable(), cardBrand: z.string().max(32).optional().nullable(), // 电子钱包特定字段 platform: z.string().max(64).optional().nullable(), platformAccountId: z.string().max(100).optional().nullable(), subChannel: z.string().max(64).optional().nullable(), subChannelType: z.enum(["CREDIT", "DEBIT"] as const).optional().nullable(), }); export type ChannelInput = z.infer; export type ChannelWithAccountNames = Channel & { linkedAccounts: { id: string; name: string }[]; }; export async function getChannelsAction(): Promise<{ success: boolean; data?: ChannelWithAccountNames[]; error?: string; }> { try { const userId = await requireUser(); const userChannels = await db .select() .from(channels) .where(and(eq(channels.userId, userId), isNull(channels.deletedAt))) .orderBy(desc(channels.createdAt)); const userAccounts = await db .select({ id: accounts.id, name: accounts.name }) .from(accounts) .where(and(eq(accounts.userId, userId), isNull(accounts.deletedAt))); const accountMap = new Map(userAccounts.map((a) => [a.id, a.name])); const result: ChannelWithAccountNames[] = userChannels.map((ch) => { const linked: { id: string; name: string }[] = []; if (Array.isArray(ch.refAccounts)) { for (const accId of ch.refAccounts) { const name = accountMap.get(accId); if (name) { linked.push({ id: accId, name }); } } } return { ...ch, linkedAccounts: linked, }; }); return { success: true, data: result }; } catch (err) { console.error("getChannelsAction error:", err); return { success: false, error: err instanceof Error ? err.message : "获取渠道失败" }; } } export async function createChannelAction( data: ChannelInput ): Promise<{ success: boolean; data?: Channel; error?: string }> { try { const userId = await requireUser(); const parsed = channelSchema.safeParse(data); if (!parsed.success) { return { success: false, error: parsed.error.issues[0]?.message || "参数错误" }; } const val = parsed.data; // 校验关联的所有账户必须属于当前登录用户 const userOwnedAccounts = await db .select({ id: accounts.id }) .from(accounts) .where( and( eq(accounts.userId, userId), inArray(accounts.id, val.refAccounts), isNull(accounts.deletedAt) ) ); if (userOwnedAccounts.length !== val.refAccounts.length) { return { success: false, error: "关联的部分账户不存在或已被删除" }; } // 自动提取或补全卡号后4位 let suffix = val.cardNumberSuffix?.trim() || null; if (!suffix && val.cardNumberFull && val.cardNumberFull.length >= 4) { suffix = val.cardNumberFull.slice(-4); } const [newChannel] = await db .insert(channels) .values({ userId, channelType: val.channelType, refAccounts: val.refAccounts, isActive: true, desc: val.desc?.trim() || null, ext: val.ext?.trim() || null, region: val.region ? val.region.toUpperCase().trim() : null, issuerName: val.issuerName?.trim() || null, cardType: val.cardType || null, cardNumberFull: val.cardNumberFull?.trim() || null, cardNumberSuffix: suffix, cardBrand: val.cardBrand?.trim() || null, platform: val.platform?.trim() || null, platformAccountId: val.platformAccountId?.trim() || null, subChannel: val.subChannel?.trim() || null, subChannelType: val.subChannelType || null, }) .returning(); revalidatePath("/channels"); revalidatePath("/accounts"); revalidatePath("/"); return { success: true, data: newChannel }; } catch (err) { console.error("createChannelAction error:", err); return { success: false, error: err instanceof Error ? err.message : "创建渠道失败" }; } } export async function updateChannelAction( id: string, data: ChannelInput ): Promise<{ success: boolean; data?: Channel; error?: string }> { try { const userId = await requireUser(); const parsed = channelSchema.safeParse(data); if (!parsed.success) { return { success: false, error: parsed.error.issues[0]?.message || "参数错误" }; } const val = parsed.data; const userOwnedAccounts = await db .select({ id: accounts.id }) .from(accounts) .where( and( eq(accounts.userId, userId), inArray(accounts.id, val.refAccounts), isNull(accounts.deletedAt) ) ); if (userOwnedAccounts.length !== val.refAccounts.length) { return { success: false, error: "关联的部分账户不存在或已被删除" }; } let suffix = val.cardNumberSuffix?.trim() || null; if (!suffix && val.cardNumberFull && val.cardNumberFull.length >= 4) { suffix = val.cardNumberFull.slice(-4); } const [updated] = await db .update(channels) .set({ channelType: val.channelType, refAccounts: val.refAccounts, desc: val.desc?.trim() || null, ext: val.ext?.trim() || null, region: val.region ? val.region.toUpperCase().trim() : null, issuerName: val.issuerName?.trim() || null, cardType: val.cardType || null, cardNumberFull: val.cardNumberFull?.trim() || null, cardNumberSuffix: suffix, cardBrand: val.cardBrand?.trim() || null, platform: val.platform?.trim() || null, platformAccountId: val.platformAccountId?.trim() || null, subChannel: val.subChannel?.trim() || null, subChannelType: val.subChannelType || null, updatedAt: new Date(), }) .where(and(eq(channels.id, id), eq(channels.userId, userId), isNull(channels.deletedAt))) .returning(); if (!updated) { return { success: false, error: "渠道不存在或无权修改" }; } revalidatePath("/channels"); revalidatePath("/accounts"); revalidatePath("/"); return { success: true, data: updated }; } catch (err) { console.error("updateChannelAction error:", err); return { success: false, error: err instanceof Error ? err.message : "更新渠道失败" }; } } export async function toggleChannelActiveAction( id: string, isActive: boolean ): Promise<{ success: boolean; error?: string }> { try { const userId = await requireUser(); await db .update(channels) .set({ isActive, updatedAt: new Date(), }) .where(and(eq(channels.id, id), eq(channels.userId, userId), isNull(channels.deletedAt))); revalidatePath("/channels"); revalidatePath("/accounts"); revalidatePath("/"); return { success: true }; } catch (err) { console.error("toggleChannelActiveAction error:", err); return { success: false, error: err instanceof Error ? err.message : "状态切换失败" }; } } export async function deleteChannelAction( id: string ): Promise<{ success: boolean; error?: string }> { try { const userId = await requireUser(); const [deleted] = await db .update(channels) .set({ deletedAt: new Date(), updatedAt: new Date(), }) .where(and(eq(channels.id, id), eq(channels.userId, userId), isNull(channels.deletedAt))) .returning(); if (!deleted) { return { success: false, error: "渠道不存在或已删除" }; } revalidatePath("/channels"); revalidatePath("/accounts"); revalidatePath("/"); return { success: true }; } catch (err) { console.error("deleteChannelAction error:", err); return { success: false, error: err instanceof Error ? err.message : "删除渠道失败" }; } }