diff --git a/components/committee/CommitteeInterviewTimes.tsx b/components/committee/CommitteeInterviewTimes.tsx index 88a98947..e05ceece 100644 --- a/components/committee/CommitteeInterviewTimes.tsx +++ b/components/committee/CommitteeInterviewTimes.tsx @@ -63,8 +63,8 @@ const CommitteeInterviewTimes = ({ useEffect(() => { if (period) { setVisibleRange({ - start: new Date(period!.interviewPeriod.start).toISOString(), - end: new Date(period!.interviewPeriod.end).toISOString(), + start: period.interviewPeriod.start.toISOString(), + end: period.interviewPeriod.end.toISOString(), }); } }, [period]); @@ -101,8 +101,8 @@ const CommitteeInterviewTimes = ({ (at: any) => ({ id: crypto.getRandomValues(new Uint32Array(1))[0].toString(), title: at.room, - start: new Date(at.start).toISOString(), - end: new Date(at.end).toISOString(), + start: at.start instanceof Date ? at.start.toISOString() : at.start, + end: at.end instanceof Date ? at.end.toISOString() : at.end, }) ); @@ -317,10 +317,9 @@ const CommitteeInterviewTimes = ({ }, [period]); const getSubmissionDeadline = (): string => { - const deadlineIso = period!.applicationPeriod.end; + const deadlineDate = period!.applicationPeriod.end; - if (deadlineIso != null && !deadLineHasPassed) { - const deadlineDate = new Date(deadlineIso); + if (deadlineDate != null && !deadLineHasPassed) { const now = new Date(); if (now > deadlineDate) { diff --git a/components/committee/Schedule.tsx b/components/committee/Schedule.tsx index 0b9f4c43..668919fd 100644 --- a/components/committee/Schedule.tsx +++ b/components/committee/Schedule.tsx @@ -18,9 +18,9 @@ interface TimeSlot { available: boolean; } -interface IsoTimeSlot { - start: string; - end: string; +interface DateTimeSlot { + start: Date; + end: Date; } export default function Schedule({ @@ -37,14 +37,24 @@ export default function Schedule({ periodTime: any, ): { [date: string]: string } => { if (!periodTime) return {}; - const startDate = new Date(periodTime.start); + const startDate = new Date( + (periodTime.start instanceof Date + ? periodTime.start + : new Date(periodTime.start) + ).getTime(), + ); startDate.setHours(startDate.getHours() + 2); - const endDate = new Date(periodTime.end); + const endDate = new Date( + (periodTime.end instanceof Date + ? periodTime.end + : new Date(periodTime.end) + ).getTime(), + ); endDate.setHours(endDate.getHours() + 2); const dates: { [date: string]: string } = {}; const dayNames = ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"]; - let currentDate = new Date(startDate); + let currentDate = new Date(startDate.getTime()); while (currentDate <= endDate) { const dayIndex = currentDate.getDay(); @@ -72,8 +82,8 @@ export default function Schedule({ return [hour, minute]; }, []); - const convertToIso = useCallback( - (date: string, timeSlot: string): IsoTimeSlot => { + const convertToDateSlot = useCallback( + (date: string, timeSlot: string): DateTimeSlot => { const [startTimeStr, endTimeStr] = timeSlot.split(" - "); const [year, month, day] = date.split("-").map(Number); @@ -88,8 +98,8 @@ export default function Schedule({ ); return { - start: startTime.toISOString(), - end: endTime.toISOString(), + start: startTime, + end: endTime, }; }, [parseTime], @@ -118,13 +128,13 @@ export default function Schedule({ }); }); - const isoTimeSlotsForExport = allAvailableTimes.map((slot) => - convertToIso(slot.date, slot.time), + const dateTimeSlotsForExport = allAvailableTimes.map((slot) => + convertToDateSlot(slot.date, slot.time), ); setApplicationData((prevData: any) => ({ ...prevData, - selectedTimes: isoTimeSlotsForExport, + selectedTimes: dateTimeSlotsForExport, })); setIsInitialized(true); }, [ @@ -132,7 +142,7 @@ export default function Schedule({ applicationData.selectedTimes, periodTime, timeSlots, - convertToIso, + convertToDateSlot, setApplicationData, ]); @@ -185,12 +195,12 @@ export default function Schedule({ }); }); - const isoTimeSlotsForExport = dataToSend.map((slot) => - convertToIso(slot.date, slot.time), + const dateTimeSlotsForExport = dataToSend.map((slot) => + convertToDateSlot(slot.date, slot.time), ); // Queue the update instead of calling setApplicationData directly - setPendingUpdate(isoTimeSlotsForExport); + setPendingUpdate(dateTimeSlotsForExport); return newCells; }); @@ -198,13 +208,17 @@ export default function Schedule({ const dates = getDatesWithinPeriod(periodTime); - let weekDates: { [date: string]: IsoTimeSlot[] } = {}; + let weekDates: { [date: string]: DateTimeSlot[] } = {}; Object.keys(dates).forEach((date) => { weekDates[date] = []; applicationData.selectedTimes?.forEach((slot) => { - if (slot?.start?.includes(date) && slot?.end) { - weekDates[date].push({ start: slot.start, end: slot.end }); + if (slot?.start && slot?.end) { + const start = new Date(slot.start as unknown as string | number | Date); + const end = new Date(slot.end as unknown as string | number | Date); + if (start.toISOString().includes(date)) { + weekDates[date].push({ start, end }); + } } }); }); diff --git a/components/committee/ScheduleColumn.tsx b/components/committee/ScheduleColumn.tsx index 3025ff05..5348e819 100644 --- a/components/committee/ScheduleColumn.tsx +++ b/components/committee/ScheduleColumn.tsx @@ -7,7 +7,7 @@ interface Props { date: string; weekDay: string; interviewLength: number; - availableSlots: { start: string; end: string }[]; + availableSlots: { start: Date; end: Date }[]; onToggleAvailability: ( date: string, time: string, @@ -27,13 +27,7 @@ export default function ScheduleColumn({ const dateOfMonth = date.split("-")[2]; const month = date.split("-")[1]; - const adjustedAvailableSlots = availableSlots.map((slot) => { - slot.start = slot.start.replace("Z", ""); - slot.end = slot.end.replace("Z", ""); - return slot; - }); - - const availableTimeSlots = convertIsoToScheduleFormat(adjustedAvailableSlots); + const availableTimeSlots = convertIsoToScheduleFormat(availableSlots); const availableTimes = availableTimeSlots.map((slot) => { let [firstTime, secondTime] = slot.time.split(" - ").map((s) => s.trim()); return `${firstTime} - ${secondTime}`; diff --git a/components/form/DatePickerInput.tsx b/components/form/DatePickerInput.tsx index 6ef4474c..a6adc6f7 100644 --- a/components/form/DatePickerInput.tsx +++ b/components/form/DatePickerInput.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; interface Props { label?: string; - updateDates: (dates: { start: string; end: string }) => void; + updateDates: (dates: { start: Date | null; end: Date | null }) => void; } const DatePickerInput = (props: Props) => { @@ -9,9 +9,9 @@ const DatePickerInput = (props: Props) => { const [toDate, setToDate] = useState(""); useEffect(() => { - const startDate = fromDate ? `${fromDate}T00:00` : ""; - const endDate = toDate ? `${toDate}T23:59` : ""; - props.updateDates({ start: startDate, end: endDate }); + const start = fromDate ? new Date(`${fromDate}T00:00`) : null; + const end = toDate ? new Date(`${toDate}T23:59`) : null; + props.updateDates({ start, end }); }, [fromDate, toDate]); return ( diff --git a/lib/api/applicantApi.ts b/lib/api/applicantApi.ts index a8166e6e..5df8620e 100644 --- a/lib/api/applicantApi.ts +++ b/lib/api/applicantApi.ts @@ -1,21 +1,34 @@ import { QueryFunctionContext } from "@tanstack/react-query"; import { applicantType } from "../types/types"; +import { parseApplicantDates } from "../utils/parseDates"; export const fetchApplicantByPeriodAndId = async ( context: QueryFunctionContext ) => { const periodId = context.queryKey[1]; const applicantId = context.queryKey[2]; - return fetch(`/api/applicants/${periodId}/${applicantId}`).then((res) => - res.json() + const data = await fetch(`/api/applicants/${periodId}/${applicantId}`).then( + (res) => res.json() ); + return { + ...data, + application: data.application + ? parseApplicantDates(data.application) + : undefined, + }; }; export const fetchApplicantsByPeriodId = async ( context: QueryFunctionContext ) => { const periodId = context.queryKey[1]; - return fetch(`/api/applicants/${periodId}`).then((res) => res.json()); + const data = await fetch(`/api/applicants/${periodId}`).then((res) => + res.json() + ); + return { + ...data, + applications: data.applications?.map(parseApplicantDates), + }; }; export const fetchApplicantsByPeriodIdAndCommittee = async ( @@ -23,9 +36,13 @@ export const fetchApplicantsByPeriodIdAndCommittee = async ( ) => { const periodId = context.queryKey[1]; const committee = context.queryKey[2]; - return fetch(`/api/committees/applicants/${periodId}/${committee}`).then( - (res) => res.json() - ); + const data = await fetch( + `/api/committees/applicants/${periodId}/${committee}` + ).then((res) => res.json()); + return { + ...data, + applicants: data.applicants?.map(parseApplicantDates), + }; }; export const createApplicant = async (applicant: applicantType) => { diff --git a/lib/api/committeesApi.ts b/lib/api/committeesApi.ts index a4fbe4c5..560cf0b4 100644 --- a/lib/api/committeesApi.ts +++ b/lib/api/committeesApi.ts @@ -1,5 +1,6 @@ import { QueryFunctionContext } from "@tanstack/react-query"; import { OwGroup } from "../types/types"; +import { parseCommitteeDates } from "../utils/parseDates"; export const fetchOwCommittees = async (): Promise => { return fetch(`/api/periods/ow-committees`).then((res) => res.json()); @@ -9,7 +10,11 @@ export const fetchCommitteeTimes = async (context: QueryFunctionContext) => { const periodId = context.queryKey[1]; const committee = context.queryKey[2]; - return fetch(`/api/committees/times/${periodId}/${committee}`).then((res) => - res.json(), - ); + const data = await fetch( + `/api/committees/times/${periodId}/${committee}`, + ).then((res) => res.json()); + return { + ...data, + committees: data.committees?.map(parseCommitteeDates), + }; }; diff --git a/lib/api/periodApi.ts b/lib/api/periodApi.ts index a5662f53..62b15bdf 100644 --- a/lib/api/periodApi.ts +++ b/lib/api/periodApi.ts @@ -1,13 +1,22 @@ import { QueryFunctionContext } from "@tanstack/react-query"; import { periodType } from "../types/types"; +import { parsePeriodDates } from "../utils/parseDates"; export const fetchPeriodById = async (context: QueryFunctionContext) => { const id = context.queryKey[1]; - return fetch(`/api/periods/${id}`).then((res) => res.json()); + const data = await fetch(`/api/periods/${id}`).then((res) => res.json()); + return { + ...data, + period: data.period ? parsePeriodDates(data.period) : undefined, + }; }; export const fetchPeriods = async () => { - return fetch(`/api/periods`).then((res) => res.json()); + const data = await fetch(`/api/periods`).then((res) => res.json()); + return { + ...data, + periods: data.periods?.map(parsePeriodDates), + }; }; export const deletePeriodById = async (id: string) => { diff --git a/lib/mongo/applicants.ts b/lib/mongo/applicants.ts index f76814ee..44427d49 100644 --- a/lib/mongo/applicants.ts +++ b/lib/mongo/applicants.ts @@ -193,16 +193,16 @@ export const getApplicantsForCommittee = async ( userCommittees.includes(preference), ); - // Skjuler søkerinformasjon for komitéen etter syv dager etter intervjuperioden + // Skjuler søkerinformasjon for komitéen etter fem dager etter intervjuperioden const today = new Date(); - const sevenDaysAfterInterviewEnd = new Date(period.interviewPeriod.end); - sevenDaysAfterInterviewEnd.setDate( - sevenDaysAfterInterviewEnd.getDate() + 5, + const fiveDaysAfterInterviewEnd = new Date(period.interviewPeriod.end.getTime()); + fiveDaysAfterInterviewEnd.setDate( + fiveDaysAfterInterviewEnd.getDate() + 5, ); if ( - new Date(period.applicationPeriod.end) > today || - today > sevenDaysAfterInterviewEnd + period.applicationPeriod.end > today || + today > fiveDaysAfterInterviewEnd ) { applicant.owId = "Skjult"; applicant.name = "Skjult"; @@ -211,7 +211,7 @@ export const getApplicantsForCommittee = async ( applicant.email = "Skjult"; applicant.about = "Skjult"; applicant.grade = "-"; - applicant.selectedTimes = [{ start: "Skjult", end: "Skjult" }]; + applicant.selectedTimes = [{ start: new Date(0), end: new Date(0) }]; } const isSelectedCommitteePresent = diff --git a/lib/mongo/periods.ts b/lib/mongo/periods.ts index dc651a61..36b9d7bd 100644 --- a/lib/mongo/periods.ts +++ b/lib/mongo/periods.ts @@ -59,17 +59,15 @@ export const getCurrentPeriods = async () => { try { if (!periods) await init(); - const currentDate = new Date().toISOString(); + const currentDate = new Date(); const filter = { $or: [ { - // Check if current ISO date string is within the application period "applicationPeriod.start": { $lte: currentDate }, "applicationPeriod.end": { $gte: currentDate }, }, { - // Check if current ISO date string is within the interview period "interviewPeriod.start": { $lte: currentDate }, "interviewPeriod.end": { $gte: currentDate }, }, diff --git a/lib/types/types.ts b/lib/types/types.ts index 11ef3693..b8637f2c 100644 --- a/lib/types/types.ts +++ b/lib/types/types.ts @@ -23,8 +23,8 @@ export type applicantType = { optionalCommittees: string[]; selectedTimes: [ { - start: string; - end: string; + start: Date; + end: Date; }, ]; date: Date; @@ -67,8 +67,8 @@ export type periodType = { }; export type AvailableTime = { - start: string; - end: string; + start: Date; + end: Date; room: string; }; diff --git a/lib/utils/convertIsoToScheduleFormat.ts b/lib/utils/convertIsoToScheduleFormat.ts index c19cd532..f8468aa1 100644 --- a/lib/utils/convertIsoToScheduleFormat.ts +++ b/lib/utils/convertIsoToScheduleFormat.ts @@ -1,6 +1,6 @@ -interface IsoTimeSlot { - start: string; - end: string; +interface TimeSlot { + start: Date; + end: Date; } interface ScheduleTimeSlot { @@ -9,24 +9,24 @@ interface ScheduleTimeSlot { } export const convertIsoToScheduleFormat = ( - isoTimeSlots: IsoTimeSlot[], + timeSlots: TimeSlot[], ): ScheduleTimeSlot[] => { - return isoTimeSlots.map((slot) => { - const startDate = new Date(slot.start); - const endDate = new Date(slot.end); + return timeSlots.map((slot) => { + const startDate = slot.start; + const endDate = slot.end; // Convert date to YYYY-MM-DD format const date = startDate.toISOString().split("T")[0]; // Convert times to HH:MM format - const startTime = startDate.toLocaleTimeString([], { + const timeFormat: Intl.DateTimeFormatOptions = { hour: "2-digit", minute: "2-digit", - }); - const endTime = endDate.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }); + hour12: false, + timeZone: "Europe/Oslo", + }; + const startTime = startDate.toLocaleTimeString("nb-NO", timeFormat); + const endTime = endDate.toLocaleTimeString("nb-NO", timeFormat); const time = `${startTime} - ${endTime}`; diff --git a/lib/utils/dateUtils.ts b/lib/utils/dateUtils.ts index 8ab5de14..198c9cce 100644 --- a/lib/utils/dateUtils.ts +++ b/lib/utils/dateUtils.ts @@ -1,55 +1,54 @@ export const formatDate = (inputDate: undefined | Date) => { - const date = new Date(inputDate || ""); + if (!inputDate) return ""; - const day = date.getDate().toString().padStart(2, "0"); - const month = (date.getMonth() + 1).toString().padStart(2, "0"); - const year = date.getFullYear(); - const hours = date.getHours().toString().padStart(2, "0"); - const minutes = date.getMinutes().toString().padStart(2, "0"); + const date = inputDate instanceof Date ? inputDate : new Date(inputDate); - return `${day}.${month}.${year}`; // - ${hours}:${minutes} + return date.toLocaleDateString("nb-NO", { + day: "2-digit", + month: "2-digit", + year: "numeric", + timeZone: "Europe/Oslo", + }); }; export const formatDateHours = ( - start: string, - end: string + start: string | Date, + end: string | Date, ) => { - const startDate = new Date(Date.parse(start)); + const startDate = start instanceof Date ? start : new Date(start); + const endDate = end instanceof Date ? end : new Date(end); + + const timeFormat: Intl.DateTimeFormatOptions = { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Europe/Oslo", + }; - const startTime = start.split("T")[1].slice(0, 5); - const endTime = end.split("T")[1].slice(0, 5); + const startTime = startDate.toLocaleTimeString("nb-NO", timeFormat); + const endTime = endDate.toLocaleTimeString("nb-NO", timeFormat); - return `${formatDateNorwegian( - startDate - )}, ${startTime} til ${endTime}`; + return `${formatDateNorwegian(startDate)}, ${startTime} til ${endTime}`; }; export const formatDateNorwegian = (inputDate?: Date | string) => { if (!inputDate) return ""; - let date: Date; - if (inputDate instanceof Date) { - date = inputDate; - } else { - date = new Date(inputDate); - } - - const day = date.getUTCDate().toString().padStart(2, "0"); - const monthsNorwegian = [ - "jan", - "feb", - "mar", - "apr", - "mai", - "jun", - "jul", - "aug", - "sep", - "okt", - "nov", - "des", - ]; - const month = monthsNorwegian[date.getUTCMonth()]; + const date = inputDate instanceof Date ? inputDate : new Date(inputDate); + + const day = date + .toLocaleDateString("nb-NO", { + day: "2-digit", + timeZone: "Europe/Oslo", + }) + .replace(".", ""); + + const month = date + .toLocaleDateString("nb-NO", { + month: "short", + timeZone: "Europe/Oslo", + }) + .replace(".", ""); return `${day}. ${month}`; }; diff --git a/lib/utils/parseDates.ts b/lib/utils/parseDates.ts new file mode 100644 index 00000000..445baabd --- /dev/null +++ b/lib/utils/parseDates.ts @@ -0,0 +1,58 @@ +import { + applicantType, + committeeInterviewType, + periodType, +} from "../types/types"; + +/** + * Parses date strings in a period object to Date objects. + * Used both server-side (after JSON.parse of req.body) and + * client-side (after fetch .json() response). + */ +export function parsePeriodDates(raw: any): periodType { + return { + ...raw, + applicationPeriod: { + start: new Date(raw.applicationPeriod.start), + end: new Date(raw.applicationPeriod.end), + }, + interviewPeriod: { + start: new Date(raw.interviewPeriod.start), + end: new Date(raw.interviewPeriod.end), + }, + }; +} + +/** + * Parses date strings in an applicant object to Date objects. + */ +export function parseApplicantDates(raw: any): applicantType { + return { + ...raw, + date: raw.date ? new Date(raw.date) : new Date(), + selectedTimes: Array.isArray(raw.selectedTimes) + ? raw.selectedTimes.map((t: any) => ({ + start: new Date(t.start), + end: new Date(t.end), + })) + : [], + }; +} + +/** + * Parses date strings in a committee interview type to Date objects. + */ +export function parseCommitteeDates( + raw: any, +): committeeInterviewType { + return { + ...raw, + availabletimes: Array.isArray(raw.availabletimes) + ? raw.availabletimes.map((t: any) => ({ + start: new Date(t.start), + end: new Date(t.end), + room: t.room, + })) + : [], + }; +} diff --git a/lib/utils/validateApplication.ts b/lib/utils/validateApplication.ts index eada7a89..7d2b52d0 100644 --- a/lib/utils/validateApplication.ts +++ b/lib/utils/validateApplication.ts @@ -71,8 +71,8 @@ export const validateApplication = (applicationData: any) => { } for (const time of selectedTimes) { - const startTime = new Date(time.start); - const endTime = new Date(time.end); + const startTime = time.start instanceof Date ? time.start : new Date(time.start); + const endTime = time.end instanceof Date ? time.end : new Date(time.end); if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) { toast.error("Ugyldig start- eller sluttid"); return false; diff --git a/lib/utils/validators.ts b/lib/utils/validators.ts index 390faa92..fe6fd7ae 100644 --- a/lib/utils/validators.ts +++ b/lib/utils/validators.ts @@ -70,14 +70,19 @@ export const isApplicantType = ( const hasSelectedTimes = Array.isArray(applicant.selectedTimes) && applicant.selectedTimes.every( - (time: { start: string; end: string }) => - typeof time.start === "string" && - typeof time.end === "string" && - new Date(time.start) >= interviewPeriodStart && - new Date(time.start) <= interviewPeriodEnd && - new Date(time.end) <= interviewPeriodEnd && - new Date(time.end) >= interviewPeriodStart && - new Date(time.start) < new Date(time.end), + (time: { start: Date | string; end: Date | string }) => { + const start = new Date(time.start); + const end = new Date(time.end); + return ( + !isNaN(start.getTime()) && + !isNaN(end.getTime()) && + start >= interviewPeriodStart && + start <= interviewPeriodEnd && + end <= interviewPeriodEnd && + end >= interviewPeriodStart && + start < end + ); + }, ); const periodOptionalCommittees = period.optionalCommittees.map((committee) => @@ -122,8 +127,8 @@ export const validateCommittee = (data: any, period: periodType): boolean => { typeof data.timeslot === "string" && Array.isArray(data.availabletimes) && data.availabletimes.every( - (time: { start: string; end: string }) => - typeof time.start === "string" && typeof time.end === "string", + (time: { start: Date; end: Date }) => + time.start instanceof Date && time.end instanceof Date, ); const isPeriodNameValid = data.periodId === String(period._id); @@ -138,16 +143,19 @@ export const validateCommittee = (data: any, period: periodType): boolean => { return committee.toLowerCase() === data.committee.toLowerCase(); }); + const intStart = new Date(period.interviewPeriod.start); + const intEnd = new Date(period.interviewPeriod.end); + const isWithinInterviewPeriod = data.availabletimes.every( - (time: { start: string; end: string }) => { + (time: { start: string | Date; end: string | Date }) => { const startTime = new Date(time.start); const endTime = new Date(time.end); return ( - startTime >= new Date(period.interviewPeriod.start) && - startTime <= new Date(period.interviewPeriod.end) && - endTime <= new Date(period.interviewPeriod.end) && - endTime >= new Date(period.interviewPeriod.start) && + startTime >= intStart && + startTime <= intEnd && + endTime <= intEnd && + endTime >= intStart && startTime < endTime ); }, diff --git a/package-lock.json b/package-lock.json index 65f637f7..a9b4b2e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "engines": { "node": ">=10" }, @@ -823,6 +824,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -839,6 +841,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -850,6 +853,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -864,6 +868,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -877,6 +882,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -885,6 +891,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -892,12 +899,14 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1074,6 +1083,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1086,6 +1096,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "engines": { "node": ">= 8" } @@ -1094,6 +1105,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1114,6 +1126,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "optional": true, "engines": { "node": ">=14" @@ -1741,18 +1754,6 @@ "typescript": ">=5.7.2" } }, - "node_modules/@trpc/server": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.6.0.tgz", - "integrity": "sha512-skTso0AWbOZck40jwNeYv++AMZXNWLUWdyk+pB5iVaYmEKTuEeMoPrEudR12VafbEU6tZa8HK3QhBfTYYHDCdg==", - "funding": [ - "https://trpc.io/sponsor" - ], - "peer": true, - "peerDependencies": { - "typescript": ">=5.7.2" - } - }, "node_modules/@trpc/tanstack-react-query": { "version": "11.6.0", "resolved": "https://registry.npmjs.org/@trpc/tanstack-react-query/-/tanstack-react-query-11.6.0.tgz", @@ -2038,6 +2039,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -2046,6 +2048,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2059,12 +2062,14 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2076,7 +2081,8 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true }, "node_modules/argparse": { "version": "2.0.1", @@ -2348,12 +2354,14 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "engines": { "node": ">=8" }, @@ -2380,6 +2388,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -2487,6 +2496,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, "engines": { "node": ">= 6" } @@ -2531,6 +2541,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2554,6 +2565,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -2575,6 +2587,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2585,7 +2598,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -2603,6 +2617,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "engines": { "node": ">= 6" } @@ -2639,6 +2654,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2662,7 +2678,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2825,7 +2842,8 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true }, "node_modules/dir-glob": { "version": "3.0.1", @@ -2842,7 +2860,8 @@ "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true }, "node_modules/doctrine": { "version": "3.0.0", @@ -2882,7 +2901,8 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -2901,7 +2921,8 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true }, "node_modules/es-abstract": { "version": "1.23.3", @@ -3565,6 +3586,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3580,6 +3602,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3624,6 +3647,7 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -3644,6 +3668,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3720,6 +3745,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -3770,6 +3796,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3894,6 +3921,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4201,6 +4229,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4240,6 +4269,7 @@ "version": "2.15.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "dev": true, "dependencies": { "hasown": "^2.0.2" }, @@ -4284,6 +4314,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -4304,6 +4335,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -4327,6 +4359,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -4362,6 +4395,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -4529,7 +4563,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/iterator.prototype": { "version": "1.1.2", @@ -4548,6 +4583,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -4562,6 +4598,7 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, "bin": { "jiti": "bin/jiti.js" } @@ -4730,6 +4767,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, "engines": { "node": ">=10" } @@ -4737,7 +4775,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/locate-path": { "version": "6.0.0", @@ -4867,6 +4906,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "engines": { "node": ">= 8" } @@ -4875,6 +4915,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -4937,6 +4978,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -5004,6 +5046,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -5159,6 +5202,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5391,7 +5435,8 @@ "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true }, "node_modules/parent-module": { "version": "1.0.1", @@ -5427,6 +5472,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -5434,12 +5480,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -5454,7 +5502,8 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -5474,6 +5523,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -5485,6 +5535,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5493,6 +5544,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, "engines": { "node": ">= 6" } @@ -5510,6 +5562,7 @@ "version": "8.4.41", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5537,6 +5590,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -5553,6 +5607,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -5571,6 +5626,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5605,6 +5661,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, "engines": { "node": ">=14" }, @@ -5616,6 +5673,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5640,6 +5698,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -5663,7 +5722,8 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true }, "node_modules/preact": { "version": "10.12.1", @@ -5755,6 +5815,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -5850,6 +5911,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -5858,6 +5920,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -5925,6 +5988,7 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -5950,6 +6014,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -5975,6 +6040,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -6106,6 +6172,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6117,6 +6184,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -6142,6 +6210,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "engines": { "node": ">=14" }, @@ -6198,6 +6267,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -6215,6 +6285,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6227,12 +6298,14 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -6244,6 +6317,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -6353,6 +6427,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6365,6 +6440,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6424,6 +6500,7 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -6445,6 +6522,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -6453,6 +6531,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -6472,6 +6551,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6509,6 +6589,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -6520,6 +6601,7 @@ "version": "3.4.9", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz", "integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==", + "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -6556,6 +6638,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "engines": { "node": ">= 6" } @@ -6564,6 +6647,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6582,6 +6666,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -6590,6 +6675,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -6601,6 +6687,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -6622,7 +6709,8 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -6780,6 +6868,7 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6891,6 +6980,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6993,6 +7083,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -7010,6 +7101,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -7025,12 +7117,14 @@ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7044,6 +7138,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -7055,6 +7150,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "engines": { "node": ">=12" }, @@ -7066,6 +7162,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -7099,6 +7196,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "dev": true, "bin": { "yaml": "bin.mjs" }, diff --git a/pages/admin/new-period.tsx b/pages/admin/new-period.tsx index 5ac74cbd..b501b64e 100644 --- a/pages/admin/new-period.tsx +++ b/pages/admin/new-period.tsx @@ -84,14 +84,14 @@ const NewPeriod = () => { start, end, }: { - start: string; - end: string; + start: Date | null; + end: Date | null; }) => { setPeriodData((prevData) => ({ ...prevData, applicationPeriod: { - start: start ? new Date(start) : undefined, - end: end ? new Date(end) : undefined, + start: start ?? undefined, + end: end ?? undefined, }, })); }; @@ -100,14 +100,14 @@ const NewPeriod = () => { start, end, }: { - start: string; - end: string; + start: Date | null; + end: Date | null; }) => { setPeriodData((prevData) => ({ ...prevData, interviewPeriod: { - start: start ? new Date(start) : undefined, - end: end ? new Date(end) : undefined, + start: start ?? undefined, + end: end ?? undefined, }, })); }; diff --git a/pages/api/applicants/[period-id]/[id].ts b/pages/api/applicants/[period-id]/[id].ts index 3c391e1b..f459f908 100644 --- a/pages/api/applicants/[period-id]/[id].ts +++ b/pages/api/applicants/[period-id]/[id].ts @@ -40,9 +40,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } return res.status(200).json({ exists, application }); } else if (req.method === "DELETE") { - const currentDate = new Date().toISOString(); - - if (new Date(period.applicationPeriod.end) < new Date(currentDate)) { + if (period.applicationPeriod.end < new Date()) { return res.status(403).json({ error: "Application period is over" }); } diff --git a/pages/api/applicants/index.ts b/pages/api/applicants/index.ts index a211142b..4886a537 100644 --- a/pages/api/applicants/index.ts +++ b/pages/api/applicants/index.ts @@ -12,6 +12,7 @@ import { isApplicantType } from "../../../lib/utils/validators"; import { isAdmin, hasSession, checkOwId } from "../../../lib/utils/apiChecks"; import { sendConfirmationSMS } from "../../../lib/sms/sendConfirmationSMS"; import { sendConfirmationEmail } from "../../../lib/email/sendConfirmationEmail"; +import { parseApplicantDates } from "../../../lib/utils/parseDates"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const session = await getServerSession(req, res, authOptions); @@ -28,8 +29,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } if (req.method === "POST") { - const requestBody: applicantType = req.body; - requestBody.date = new Date(new Date().getTime() + 60 * 60 * 2000); // add date with norwegain time (GMT+2) + const requestBody = parseApplicantDates(req.body); + requestBody.date = new Date(); const { period } = await getPeriodById(String(requestBody.periodId)); @@ -37,7 +38,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(400).json({ error: "Invalid period id" }); } - if (!isApplicantType(req.body, period)) { + if (!isApplicantType(requestBody, period)) { return res.status(400).json({ error: "Invalid data format" }); } @@ -79,8 +80,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } if (req.method === "PUT") { - const requestBody: applicantType = req.body; - requestBody.date = new Date(new Date().getTime() + 60 * 60 * 2000); // add date with norwegian time (GMT+2). TODO: Fix workaround in #415 + const requestBody = parseApplicantDates(req.body); + requestBody.date = new Date(); // Remove _id field to prevent MongoDB immutable field error if ("_id" in requestBody) { @@ -93,7 +94,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(400).json({ error: "Invalid period id" }); } - if (!isApplicantType(req.body, period)) { + if (!isApplicantType(requestBody, period)) { return res.status(400).json({ error: "Invalid data format" }); } diff --git a/pages/api/committees/times/[period-id]/[committee].ts b/pages/api/committees/times/[period-id]/[committee].ts index 573b31ed..cdddf2a4 100644 --- a/pages/api/committees/times/[period-id]/[committee].ts +++ b/pages/api/committees/times/[period-id]/[committee].ts @@ -50,7 +50,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(400).json({ error: "Invalid periodId" }); } - if (new Date() > new Date(period.applicationPeriod.end)) { + if (new Date() > period.applicationPeriod.end) { return res.status(400).json({ error: "Application period has ended" }); } diff --git a/pages/api/committees/times/[period-id]/index.ts b/pages/api/committees/times/[period-id]/index.ts index 297a0016..2570c8be 100644 --- a/pages/api/committees/times/[period-id]/index.ts +++ b/pages/api/committees/times/[period-id]/index.ts @@ -9,6 +9,7 @@ import { } from "../../../../../lib/utils/validators"; import { getPeriodById } from "../../../../../lib/mongo/periods"; import { committeeInterviewType } from "../../../../../lib/types/types"; +import { parseCommitteeDates } from "../../../../../lib/utils/parseDates"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const session = await getServerSession(req, res, authOptions); @@ -24,18 +25,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (!isInCommitee(res, session)) return; if (req.method === "POST") { - const committeeData: committeeInterviewType = req.body; - if (!isCommitteeType(req.body)) { return res.status(400).json({ error: "Invalid data format" }); } + const committeeData = parseCommitteeDates(req.body); + const { period } = await getPeriodById(String(committeeData.periodId)); if (!period) { return res.status(400).json({ error: "Invalid periodId" }); } - if (new Date() > new Date(period.applicationPeriod.end)) { + if (new Date() > period.applicationPeriod.end) { return res.status(400).json({ error: "Application period has ended" }); } diff --git a/pages/api/periods/index.ts b/pages/api/periods/index.ts index 19064c26..46c691af 100644 --- a/pages/api/periods/index.ts +++ b/pages/api/periods/index.ts @@ -5,6 +5,7 @@ import { periodType } from "../../../lib/types/types"; import { createPeriod, getPeriods } from "../../../lib/mongo/periods"; import { hasSession, isAdmin } from "../../../lib/utils/apiChecks"; import { isPeriodType } from "../../../lib/utils/validators"; +import { parsePeriodDates } from "../../../lib/utils/parseDates"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const session = await getServerSession(req, res, authOptions); @@ -22,12 +23,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === "POST") { if (!isAdmin(res, session)) return; - const period = req.body as periodType; if (!isPeriodType(req.body)) { return res.status(400).json({ error: "Invalid data format" }); } + const period = parsePeriodDates(req.body); + const { error } = await createPeriod(period); if (error) throw new Error(error); return res.status(201).json({ message: "Period created successfully" }); diff --git a/pages/apply/[period-id].tsx b/pages/apply/[period-id].tsx index 92d55b6a..5035e4c2 100644 --- a/pages/apply/[period-id].tsx +++ b/pages/apply/[period-id].tsx @@ -151,10 +151,7 @@ const Application: NextPage = () => { setPeriod(periodData.period); - const currentDate = new Date().toISOString(); - if ( - new Date(periodData.period.applicationPeriod.end) < new Date(currentDate) - ) { + if (periodData.period.applicationPeriod.end < new Date()) { setIsApplicationPeriodOver(true); } }, [periodData]); diff --git a/pages/apply/index.tsx b/pages/apply/index.tsx index 4a7867bf..80c7df29 100644 --- a/pages/apply/index.tsx +++ b/pages/apply/index.tsx @@ -28,18 +28,13 @@ const Apply = () => { setCurrentPeriods( periodsData.periods.filter((period: periodType) => { - const startDate = new Date(period.applicationPeriod.start || ""); - const endDate = new Date(period.applicationPeriod.end || ""); - - return startDate <= today && endDate >= today; + return period.applicationPeriod.start <= today && period.applicationPeriod.end >= today; }) ); setUpcomingPeriods( periodsData.periods.filter((period: periodType) => { - const startDate = new Date(period.applicationPeriod.start || ""); - - return startDate >= today + return period.applicationPeriod.start >= today; }) ) }, [periodsData]); diff --git a/pages/committee/[period-id]/[committee]/index.tsx b/pages/committee/[period-id]/[committee]/index.tsx index b5f51989..ddf9ca10 100644 --- a/pages/committee/[period-id]/[committee]/index.tsx +++ b/pages/committee/[period-id]/[committee]/index.tsx @@ -82,9 +82,7 @@ const CommitteeApplicantOverview: NextPage = () => { if (periodIsError || interviewTimesIsError) return ; if (!hasAccess) return ; - const interviewPeriodEnd = period?.interviewPeriod.end - ? new Date(period.interviewPeriod.end) - : null; + const interviewPeriodEnd = period?.interviewPeriod.end ?? null; // Satt frist til 14 dager etter intervju perioden, så får man ikke tilgang const interviewAccessExpired = diff --git a/pages/committees.tsx b/pages/committees.tsx index b658841c..97f1be2a 100644 --- a/pages/committees.tsx +++ b/pages/committees.tsx @@ -168,9 +168,7 @@ const committeeIsInActivePeriod = ( const today = new Date(); const activePeriods = periods.filter((period) => { - const applicationStart = new Date(period.applicationPeriod.start); - const applicationEnd = new Date(period.applicationPeriod.end); - return applicationStart <= today && applicationEnd >= today; + return period.applicationPeriod.start <= today && period.applicationPeriod.end >= today; }); // Bankom is always active, since you can be a representative of bankom from each committee @@ -194,9 +192,7 @@ const committeeIsCurrentlyInterviewing = ( const today = new Date(); const periodsWithInterviewsCurrently = periods.filter((period) => { - const interviewStart = new Date(period.interviewPeriod.start); - const interviewEnd = new Date(period.interviewPeriod.end); - return interviewStart <= today && interviewEnd >= today; + return period.interviewPeriod.start <= today && period.interviewPeriod.end >= today; }); // Bankom is always active, since you can be a representative of bankom from each committee diff --git a/scripts/migrate-dates.js b/scripts/migrate-dates.js new file mode 100644 index 00000000..61ddc52d --- /dev/null +++ b/scripts/migrate-dates.js @@ -0,0 +1,152 @@ +/** + * Migreringsscript: Konverterer streng-datoer til BSON Date i MongoDB. + * + * Kjør med: node scripts/migrate-dates.js + * + * Krever MONGODB_URI miljøvariabel (les fra .env.local eller sett manuelt). + * + * Alternativt kan du kjøre innholdet i migrateCollections() direkte i mongosh. + */ + +const { MongoClient } = require("mongodb"); +const path = require("path"); +const fs = require("fs"); + +// Forsøk å lese MONGODB_URI fra .env.local +function loadEnv() { + const envPath = path.resolve(__dirname, "../.env.local"); + if (fs.existsSync(envPath)) { + const content = fs.readFileSync(envPath, "utf-8"); + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const [key, ...rest] = trimmed.split("="); + if (key && rest.length > 0) { + process.env[key.trim()] = rest.join("=").trim(); + } + } + } + } +} + +loadEnv(); + +const URI = process.env.MONGODB_URI; +if (!URI) { + console.error("MONGODB_URI er ikke satt. Sett den i .env.local eller som miljøvariabel."); + process.exit(1); +} + +async function migratePeriods(db) { + const periods = db.collection("periods"); + const docs = await periods.find({}).toArray(); + let updated = 0; + + for (const doc of docs) { + const needsUpdate = + typeof doc.applicationPeriod?.start === "string" || + typeof doc.applicationPeriod?.end === "string" || + typeof doc.interviewPeriod?.start === "string" || + typeof doc.interviewPeriod?.end === "string"; + + if (!needsUpdate) continue; + + await periods.updateOne( + { _id: doc._id }, + { + $set: { + "applicationPeriod.start": new Date(doc.applicationPeriod.start), + "applicationPeriod.end": new Date(doc.applicationPeriod.end), + "interviewPeriod.start": new Date(doc.interviewPeriod.start), + "interviewPeriod.end": new Date(doc.interviewPeriod.end), + }, + } + ); + updated++; + } + + console.log(`periods: ${updated}/${docs.length} dokumenter oppdatert`); +} + +async function migrateApplications(db) { + const applications = db.collection("applications"); + const docs = await applications.find({}).toArray(); + let updated = 0; + + for (const doc of docs) { + if (!Array.isArray(doc.selectedTimes) || doc.selectedTimes.length === 0) continue; + + const needsUpdate = doc.selectedTimes.some( + (t) => typeof t.start === "string" || typeof t.end === "string" + ); + + if (!needsUpdate) continue; + + const updatedTimes = doc.selectedTimes.map((t) => ({ + start: typeof t.start === "string" ? new Date(t.start) : t.start, + end: typeof t.end === "string" ? new Date(t.end) : t.end, + })); + + await applications.updateOne( + { _id: doc._id }, + { $set: { selectedTimes: updatedTimes } } + ); + updated++; + } + + console.log(`applications: ${updated}/${docs.length} dokumenter oppdatert`); +} + +async function migrateCommittees(db) { + const committees = db.collection("committees"); + const docs = await committees.find({}).toArray(); + let updated = 0; + + for (const doc of docs) { + if (!Array.isArray(doc.availabletimes) || doc.availabletimes.length === 0) continue; + + const needsUpdate = doc.availabletimes.some( + (t) => typeof t.start === "string" || typeof t.end === "string" + ); + + if (!needsUpdate) continue; + + const updatedTimes = doc.availabletimes.map((t) => ({ + start: typeof t.start === "string" ? new Date(t.start) : t.start, + end: typeof t.end === "string" ? new Date(t.end) : t.end, + room: t.room, + })); + + await committees.updateOne( + { _id: doc._id }, + { $set: { availabletimes: updatedTimes } } + ); + updated++; + } + + console.log(`committees: ${updated}/${docs.length} dokumenter oppdatert`); +} + +async function main() { + const client = new MongoClient(URI); + + try { + await client.connect(); + const db = client.db(); + + console.log("Starter migrering av datoer...\n"); + + await migratePeriods(db); + await migrateApplications(db); + await migrateCommittees(db); + + console.log("\nMigrering fullført!"); + } catch (error) { + console.error("Feil under migrering:", error); + process.exit(1); + } finally { + await client.close(); + } +} + +main(); diff --git a/scripts/migrate-to-new-db.js b/scripts/migrate-to-new-db.js new file mode 100644 index 00000000..14a3235b --- /dev/null +++ b/scripts/migrate-to-new-db.js @@ -0,0 +1,139 @@ +/** + * Bygger Date_dev som en migrert kopi av kilde-DB-en (fra MONGODB_URI) + * via aggregation + $out. Kilde-DB-en berøres ikke. + * + * Kjør med: node scripts/migrate-to-new-db.js + */ + +const { MongoClient } = require("mongodb"); +const path = require("path"); +const fs = require("fs"); + +function loadEnv() { + const envPath = path.resolve(__dirname, "../.env.local"); + if (fs.existsSync(envPath)) { + const content = fs.readFileSync(envPath, "utf-8"); + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const [key, ...rest] = trimmed.split("="); + if (key && rest.length > 0) { + process.env[key.trim()] = rest.join("=").trim(); + } + } + } + } +} + +loadEnv(); + +const URI = process.env.MONGODB_URI; +if (!URI) { + console.error("MONGODB_URI er ikke satt. Sett den i .env.local eller som miljøvariabel."); + process.exit(1); +} + +const TARGET_DB = "Date_dev"; + +async function runPipeline(sourceDb, sourceColl, pipeline) { + await sourceDb.collection(sourceColl).aggregate(pipeline).toArray(); +} + +async function main() { + const client = new MongoClient(URI); + + try { + await client.connect(); + const sourceDb = client.db(); + const targetDb = client.db(TARGET_DB); + + console.log(`Kilde-DB: ${sourceDb.databaseName}`); + console.log(`Mål-DB: ${TARGET_DB}\n`); + + console.log("Migrerer periods..."); + await runPipeline(sourceDb, "periods", [ + { + $addFields: { + "applicationPeriod.start": { $toDate: "$applicationPeriod.start" }, + "applicationPeriod.end": { $toDate: "$applicationPeriod.end" }, + "interviewPeriod.start": { $toDate: "$interviewPeriod.start" }, + "interviewPeriod.end": { $toDate: "$interviewPeriod.end" }, + }, + }, + { $out: { db: TARGET_DB, coll: "periods" } }, + ]); + console.log( + ` → ${TARGET_DB}.periods: ${await targetDb.collection("periods").countDocuments()} dokumenter\n` + ); + + console.log("Migrerer applications..."); + await runPipeline(sourceDb, "applications", [ + { + $addFields: { + selectedTimes: { + $map: { + input: "$selectedTimes", + as: "t", + in: { + start: { $toDate: "$$t.start" }, + end: { $toDate: "$$t.end" }, + }, + }, + }, + }, + }, + { $out: { db: TARGET_DB, coll: "applications" } }, + ]); + console.log( + ` → ${TARGET_DB}.applications: ${await targetDb.collection("applications").countDocuments()} dokumenter\n` + ); + + console.log("Migrerer committees..."); + await runPipeline(sourceDb, "committees", [ + { + $addFields: { + availabletimes: { + $map: { + input: "$availabletimes", + as: "t", + in: { + start: { $toDate: "$$t.start" }, + end: { $toDate: "$$t.end" }, + room: "$$t.room", + }, + }, + }, + }, + }, + { $out: { db: TARGET_DB, coll: "committees" } }, + ]); + console.log( + ` → ${TARGET_DB}.committees: ${await targetDb.collection("committees").countDocuments()} dokumenter\n` + ); + + console.log("Kopierer interviews..."); + await runPipeline(sourceDb, "interviews", [ + { $out: { db: TARGET_DB, coll: "interviews" } }, + ]); + console.log( + ` → ${TARGET_DB}.interviews: ${await targetDb.collection("interviews").countDocuments()} dokumenter\n` + ); + + console.log("Kopierer rooms..."); + await runPipeline(sourceDb, "rooms", [ + { $out: { db: TARGET_DB, coll: "rooms" } }, + ]); + console.log( + ` → ${TARGET_DB}.rooms: ${await targetDb.collection("rooms").countDocuments()} dokumenter\n` + ); + + console.log("Migrering fullført!"); + } catch (error) { + console.error("Feil under migrering:", error); + process.exit(1); + } finally { + await client.close(); + } +} + +main();