import { prisma } from "@/lib/db"; export async function getShareLink(token: string) { const link = await prisma.shareLink.findUnique({ where: { id: token }, include: { deck: { include: { cards: { orderBy: { sortOrder: "asc" } }, class: { select: { slug: true, name: true } }, }, }, quizSet: { include: { questions: { orderBy: { sortOrder: "asc" }, include: { options: { orderBy: { sortOrder: "asc" } }, }, }, class: { select: { slug: true, name: true } }, }, }, group: { include: { class: { select: { slug: true, name: true } }, decks: { include: { cards: { orderBy: { sortOrder: "asc" } }, class: { select: { slug: true, name: true } }, }, orderBy: { sortOrder: "asc" }, }, quizSets: { include: { questions: { orderBy: { sortOrder: "asc" }, include: { options: { orderBy: { sortOrder: "asc" } }, }, }, class: { select: { slug: true, name: true } }, }, orderBy: { sortOrder: "asc" }, }, } } }, }); if (!link) return null; const targets = [link.deck, link.quizSet, link.group].filter(Boolean); const agrees = (link.targetType === "DECK" && Boolean(link.deck)) || (link.targetType === "QUIZ" && Boolean(link.quizSet)) || (link.targetType === "GROUP" && Boolean(link.group)); if (targets.length !== 1 || !agrees) return null; if (!link.group) return link; return { ...link, group: { ...link.group, decks: link.group.decks.filter( (deck) => link.group?.type === "DECK" && deck.class.slug === link.group.class.slug ), quizSets: link.group.quizSets.filter( (quiz) => link.group?.type === "QUIZ" && quiz.class.slug === link.group.class.slug ), }, }; } /** * Names, descriptions and counts only — enough to build social/link-preview * metadata without loading every card, question and option. */ export async function getShareLinkMeta(token: string) { const link = await prisma.shareLink.findUnique({ where: { id: token }, select: { targetType: true, deck: { select: { name: true, description: true, class: { select: { slug: true, name: true } }, _count: { select: { cards: true } }, }, }, quizSet: { select: { name: true, description: true, class: { select: { slug: true, name: true } }, _count: { select: { questions: true } }, }, }, group: { select: { name: true, type: true, class: { select: { slug: true, name: true } }, decks: { select: { id: true, name: true, description: true, class: { select: { slug: true, name: true } }, _count: { select: { cards: true } }, }, orderBy: { sortOrder: "asc" }, }, quizSets: { select: { id: true, name: true, description: true, class: { select: { slug: true, name: true } }, _count: { select: { questions: true } }, }, orderBy: { sortOrder: "asc" }, }, }, }, }, }); if (!link) return null; const targets = [link.deck, link.quizSet, link.group].filter(Boolean); const agrees = (link.targetType === "DECK" && Boolean(link.deck)) || (link.targetType === "QUIZ" && Boolean(link.quizSet)) || (link.targetType === "GROUP" && Boolean(link.group)); if (targets.length !== 1 || !agrees) return null; if (!link.group) return link; return { ...link, group: { ...link.group, decks: link.group.decks.filter( (deck) => link.group?.type === "DECK" && deck.class.slug === link.group.class.slug ), quizSets: link.group.quizSets.filter( (quiz) => link.group?.type === "QUIZ" && quiz.class.slug === link.group.class.slug ), }, }; } export async function getShareLinkForContent(targetType: "DECK" | "QUIZ" | "GROUP", contentId: string) { if (targetType === "DECK") { return prisma.shareLink.findUnique({ where: { deckId: contentId } }); } if (targetType === "GROUP") { return prisma.shareLink.findUnique({ where: { groupId: contentId } }); } return prisma.shareLink.findUnique({ where: { quizSetId: contentId } }); } export async function toggleShareLink(targetType: "DECK" | "QUIZ" | "GROUP", contentId: string) { return prisma.$transaction(async (transaction) => { const target = targetType === "DECK" ? await transaction.deck.findUnique({ where: { id: contentId }, select: { id: true }, }) : targetType === "QUIZ" ? await transaction.quizSet.findUnique({ where: { id: contentId }, select: { id: true }, }) : await transaction.materialGroup.findUnique({ where: { id: contentId }, select: { id: true }, }); if (!target) throw new ShareTargetNotFoundError("Share target not found"); const existing = targetType === "DECK" ? await transaction.shareLink.findUnique({ where: { deckId: contentId } }) : targetType === "QUIZ" ? await transaction.shareLink.findUnique({ where: { quizSetId: contentId } }) : await transaction.shareLink.findUnique({ where: { groupId: contentId } }); if (existing) { await transaction.shareLink.delete({ where: { id: existing.id } }); return null; } return transaction.shareLink.create({ data: { targetType, deckId: targetType === "DECK" ? contentId : null, quizSetId: targetType === "QUIZ" ? contentId : null, groupId: targetType === "GROUP" ? contentId : null, }, }); }); } export class ShareTargetNotFoundError extends Error {} export async function isContentSharedViaGroup(targetType: "DECK" | "QUIZ", contentId: string) { let groupId: string | null = null; if (targetType === "DECK") { const deck = await prisma.deck.findUnique({ where: { id: contentId }, select: { groupId: true } }); groupId = deck?.groupId || null; } else { const quiz = await prisma.quizSet.findUnique({ where: { id: contentId }, select: { groupId: true } }); groupId = quiz?.groupId || null; } if (!groupId) return null; const groupLink = await prisma.shareLink.findUnique({ where: { groupId }, include: { group: true }, }); return groupLink ? { token: groupLink.id, groupName: groupLink.group?.name } : null; }