"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 : "删除渠道失败", } } }