Implement local browser caching for shared groups and UI improvements
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m10s

This commit is contained in:
Elijah 2026-07-10 16:01:22 -07:00
parent 42ed0d275b
commit 7af0935657
37 changed files with 6141 additions and 411 deletions

View file

@ -33,10 +33,11 @@ export async function getDeckWithCards(deckId: string) {
export async function createDeckFromImport(
classId: string,
data: FlashcardImportData,
overrideName?: string
overrideName?: string,
groupId?: string | null
) {
const maxOrder = await prisma.deck.aggregate({
where: { classId },
where: { classId, groupId: groupId || null },
_max: { sortOrder: true },
});
const sortOrder = (maxOrder._max.sortOrder ?? -1) + 1;
@ -44,6 +45,7 @@ export async function createDeckFromImport(
return prisma.deck.create({
data: {
classId,
groupId: groupId || null,
name: overrideName || data.deckName,
description: data.description,
sortOrder,

View file

@ -38,10 +38,11 @@ export async function getQuizSetWithQuestions(quizSetId: string) {
export async function createQuizSetFromImport(
classId: string,
data: QuizImportData,
overrideName?: string
overrideName?: string,
groupId?: string | null
) {
const maxOrder = await prisma.quizSet.aggregate({
where: { classId },
where: { classId, groupId: groupId || null },
_max: { sortOrder: true },
});
const sortOrder = (maxOrder._max.sortOrder ?? -1) + 1;
@ -49,6 +50,7 @@ export async function createQuizSetFromImport(
return prisma.quizSet.create({
data: {
classId,
groupId: groupId || null,
name: overrideName || data.quizName,
description: data.description,
sortOrder,

View file

@ -21,18 +21,45 @@ export async function getShareLink(token: string) {
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" },
},
}
}
},
});
}
export async function getShareLinkForContent(targetType: "DECK" | "QUIZ", contentId: string) {
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", contentId: string) {
export async function toggleShareLink(targetType: "DECK" | "QUIZ" | "GROUP", contentId: string) {
const existing = await getShareLinkForContent(targetType, contentId);
if (existing) {
@ -47,6 +74,28 @@ export async function toggleShareLink(targetType: "DECK" | "QUIZ", contentId: st
targetType,
deckId: targetType === "DECK" ? contentId : null,
quizSetId: targetType === "QUIZ" ? contentId : null,
groupId: targetType === "GROUP" ? contentId : null,
},
});
}
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;
}