227 lines
6.9 KiB
TypeScript
227 lines
6.9 KiB
TypeScript
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;
|
|
}
|