Add schedule validation and service layer
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// Schedule service layer. Owns the database orchestration and enforcement of
|
||||
// the Block business rules (at most 4 per user, at least one Proclamation,
|
||||
// no overnight ranges, no overlaps). Pure validation lives in
|
||||
// `$lib/schedule-utils` so the client modal can reuse it.
|
||||
|
||||
import { prisma } from '$lib/db';
|
||||
import { validateBlock, type BlockInput } from '$lib/schedule-utils';
|
||||
|
||||
export {
|
||||
MAX_BLOCKS,
|
||||
MINUTES_PER_DAY,
|
||||
SNAP_MINUTES,
|
||||
MINUTE_STEP,
|
||||
formatTime,
|
||||
toHm,
|
||||
snapMinutes,
|
||||
findOverlap,
|
||||
validateBlock
|
||||
} from '$lib/schedule-utils';
|
||||
export type { BlockInput, BlockSibling, ValidationResult } from '$lib/schedule-utils';
|
||||
|
||||
export type BlockMutationResult = { ok: true; id?: string } | { ok: false; error: string };
|
||||
|
||||
function uniqueIds(ids: string[]): string[] {
|
||||
return [...new Set(ids.filter((id) => typeof id === 'string' && id.length > 0))];
|
||||
}
|
||||
|
||||
/** Ensure the user has a Schedule row, creating the singleton on first use. */
|
||||
export async function getOrCreateSchedule(userId: string) {
|
||||
const existing = await prisma.schedule.findUnique({ where: { userId } });
|
||||
if (existing) return existing;
|
||||
return prisma.schedule.create({ data: { userId } });
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's Blocks with their selected Proclamations (and frequencies for
|
||||
* canonical-id mapping), ordered by start time.
|
||||
*/
|
||||
export async function listBlocks(userId: string) {
|
||||
return prisma.block.findMany({
|
||||
where: { schedule: { userId } },
|
||||
orderBy: { startMinutes: 'asc' },
|
||||
include: {
|
||||
proclamations: {
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
include: {
|
||||
proclamation: { include: { solfeggioFrequency: true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBlock(userId: string, input: BlockInput): Promise<BlockMutationResult> {
|
||||
const schedule = await getOrCreateSchedule(userId);
|
||||
const siblings = await prisma.block.findMany({
|
||||
where: { scheduleId: schedule.id },
|
||||
select: { id: true, startMinutes: true, endMinutes: true }
|
||||
});
|
||||
|
||||
const validation = validateBlock(input, siblings, { isCreate: true });
|
||||
if (!validation.ok) return validation;
|
||||
|
||||
const proclamationIds = uniqueIds(input.proclamationIds);
|
||||
const block = await prisma.block.create({
|
||||
data: {
|
||||
scheduleId: schedule.id,
|
||||
startMinutes: input.startMinutes,
|
||||
endMinutes: input.endMinutes,
|
||||
proclamations: {
|
||||
create: proclamationIds.map((proclamationId, index) => ({
|
||||
proclamationId,
|
||||
sortOrder: index
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { ok: true, id: block.id };
|
||||
}
|
||||
|
||||
export async function updateBlock(
|
||||
userId: string,
|
||||
blockId: string,
|
||||
input: BlockInput
|
||||
): Promise<BlockMutationResult> {
|
||||
const block = await prisma.block.findFirst({
|
||||
where: { id: blockId, schedule: { userId } },
|
||||
select: { id: true, scheduleId: true }
|
||||
});
|
||||
if (!block) return { ok: false, error: 'Block not found.' };
|
||||
|
||||
const siblings = await prisma.block.findMany({
|
||||
where: { scheduleId: block.scheduleId, id: { not: blockId } },
|
||||
select: { id: true, startMinutes: true, endMinutes: true }
|
||||
});
|
||||
|
||||
const validation = validateBlock(input, siblings);
|
||||
if (!validation.ok) return validation;
|
||||
|
||||
const proclamationIds = uniqueIds(input.proclamationIds);
|
||||
await prisma.block.update({
|
||||
where: { id: blockId },
|
||||
data: {
|
||||
startMinutes: input.startMinutes,
|
||||
endMinutes: input.endMinutes,
|
||||
proclamations: {
|
||||
deleteMany: {},
|
||||
create: proclamationIds.map((proclamationId, index) => ({
|
||||
proclamationId,
|
||||
sortOrder: index
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { ok: true, id: blockId };
|
||||
}
|
||||
|
||||
export async function deleteBlock(userId: string, blockId: string): Promise<BlockMutationResult> {
|
||||
const block = await prisma.block.findFirst({
|
||||
where: { id: blockId, schedule: { userId } },
|
||||
select: { id: true }
|
||||
});
|
||||
if (!block) return { ok: false, error: 'Block not found.' };
|
||||
|
||||
await prisma.block.delete({ where: { id: blockId } });
|
||||
return { ok: true, id: blockId };
|
||||
}
|
||||
Reference in New Issue
Block a user