95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import "server-only";
|
|
|
|
import { prisma } from "@/lib/db";
|
|
import { categoryAccessFilter } from "@/lib/access";
|
|
import type { CategoryDTO } from "@/types/board";
|
|
import type { AiGroupContext } from "@/types/ai";
|
|
|
|
/**
|
|
* Fetches one board's worth of categories/groups/todos, shaped for the
|
|
* client -- shared by the Home board and every Project board, which are
|
|
* identical in every way except which categories they scope to.
|
|
*/
|
|
export async function getBoard(
|
|
where: { userId: string; projectId: null } | { projectId: string }
|
|
): Promise<CategoryDTO[]> {
|
|
const categories = await prisma.category.findMany({
|
|
where,
|
|
orderBy: { order: "asc" },
|
|
include: {
|
|
// Archived groups are kept in the database (not deleted) but
|
|
// excluded from the board query.
|
|
groups: {
|
|
where: { archivedAt: null },
|
|
orderBy: { order: "asc" },
|
|
include: { todos: { orderBy: { order: "asc" } } },
|
|
},
|
|
},
|
|
});
|
|
|
|
return categories.map((category) => ({
|
|
id: category.id,
|
|
name: category.name,
|
|
order: category.order,
|
|
groups: category.groups.map((group) => ({
|
|
id: group.id,
|
|
title: group.title,
|
|
color: group.color,
|
|
order: group.order,
|
|
noteContent: group.noteContent,
|
|
categoryId: group.categoryId,
|
|
todos: group.todos.map((todo) => ({
|
|
id: todo.id,
|
|
title: todo.title,
|
|
details: todo.details,
|
|
completed: todo.completed,
|
|
order: todo.order,
|
|
groupId: todo.groupId,
|
|
createdAt: todo.createdAt.toISOString(),
|
|
updatedAt: todo.updatedAt.toISOString(),
|
|
completedAt: todo.completedAt?.toISOString() ?? null,
|
|
})),
|
|
})),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Every group the user can see across every board they own -- the Home
|
|
* board plus every Project (see lib/access.ts's categoryAccessFilter) --
|
|
* flattened into one list for feeding to the AI as context (see
|
|
* lib/ai/context.ts). Unlike getBoard, archived groups are deliberately
|
|
* *not* filtered out: the global chat feature needs to answer historical
|
|
* questions about finished work, not just describe what's currently on
|
|
* screen. One query, not one getBoard() call per project.
|
|
*/
|
|
export async function getAllGroupsForUser(userId: string): Promise<AiGroupContext[]> {
|
|
const categories = await prisma.category.findMany({
|
|
where: categoryAccessFilter(userId),
|
|
orderBy: { order: "asc" },
|
|
include: {
|
|
project: { select: { title: true } },
|
|
groups: {
|
|
orderBy: { order: "asc" },
|
|
include: { todos: { orderBy: { order: "asc" } } },
|
|
},
|
|
},
|
|
});
|
|
|
|
return categories.flatMap((category) =>
|
|
category.groups.map((group) => ({
|
|
groupId: group.id,
|
|
groupTitle: group.title,
|
|
scopeLabel: category.project?.title ?? "Home",
|
|
categoryName: category.name,
|
|
archived: group.archivedAt !== null,
|
|
noteContent: group.noteContent,
|
|
todos: group.todos.map((todo) => ({
|
|
title: todo.title,
|
|
details: todo.details,
|
|
completed: todo.completed,
|
|
completedAt: todo.completedAt?.toISOString() ?? null,
|
|
})),
|
|
}))
|
|
);
|
|
}
|