From f282ce84f92c2c03ddb7320113a6c9d51402c438 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Fri, 8 May 2026 22:04:50 -0500 Subject: [PATCH 01/18] initial --- server/api/form/listForms.get.ts | 9 +++++++++ server/api/formGroup/listFormGroups.get.ts | 0 2 files changed, 9 insertions(+) create mode 100644 server/api/form/listForms.get.ts create mode 100644 server/api/formGroup/listFormGroups.get.ts diff --git a/server/api/form/listForms.get.ts b/server/api/form/listForms.get.ts new file mode 100644 index 0000000..b56e076 --- /dev/null +++ b/server/api/form/listForms.get.ts @@ -0,0 +1,9 @@ +import { prisma } from '../../utils/prisma' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' + + +export default defineEventHandler(async (event) => { + + + }) \ No newline at end of file diff --git a/server/api/formGroup/listFormGroups.get.ts b/server/api/formGroup/listFormGroups.get.ts new file mode 100644 index 0000000..e69de29 From 34bc092dafa4c2f7400f46aa2ceff5cb2259ffa4 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Mon, 1 Jun 2026 15:24:24 -0500 Subject: [PATCH 02/18] almost finished splitting listForms action and updating admin composable --- app/composables/useAdmin.ts | 19 ++-- server/api/form/index.ts | 98 -------------------- server/api/form/listForms.get.ts | 52 ++++++++++- server/api/form/testgetall.get.ts | 22 +++++ server/api/formGroup/MatchGroupByDate.get.ts | 10 ++ server/utils/schemas.ts | 5 - 6 files changed, 89 insertions(+), 117 deletions(-) create mode 100644 server/api/form/testgetall.get.ts create mode 100644 server/api/formGroup/MatchGroupByDate.get.ts diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index 685b6bb..9d183ef 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -83,17 +83,18 @@ export const useAdmin = () => { const mapApiFormToUi = (form: any) => { const questionList = Array.isArray(form.questions) ? form.questions : [] + //replace direct type casting with better alternative, maybe zod coercing? return { id: Number(form.id), - weekStart: parseDateToYmd(form.weekStart || form.startDate || ''), - day: form.day || 'Monday', - title: form.title || `Form ${form.id}`, - date: form.date || formatDate(form.startDate || ''), - status: form.status || (form.published ? 'Active' : 'Unpublished'), - questions: questionList.map((question: any, index: number) => ({ - id: Number(question.id ?? index + 1), - type: question.type || question.questionType || 'text', - text: question.text || question.questionText || '', + weekStart: parseDateToYmd(form.startDate), //update to dayjs format function + day: dayjs(form.startDate).format('dddd'), + title: form.title, + date: formatDate(form.startDate || ''), //update to dayjs format function + status: form.published ? 'Active' : 'Unpublished', + questions: form.Components.slice().sort((left, right) => left.order - right.order).map((question: any) => ({ + id: question.id, + type: question.questionType || 'text', + text: question.questionText || '', textEs: question.questionOptions?.textEs || '', reference: question.questionOptions?.reference || '', referenceEs: question.questionOptions?.referenceEs || '', diff --git a/server/api/form/index.ts b/server/api/form/index.ts index f78e1e0..f8fd8af 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -9,7 +9,6 @@ type ActionName = | 'getFormGroup' | 'resolveFormGroupRangeByDate' | 'getFormGroupSubmissions' - | 'listForms' | 'createFormGroup' | 'createForm' | 'createComponent' @@ -27,67 +26,6 @@ const hasOwnField = (value: Record | null, key: string) => const normalizeScalar = (value: unknown) => (Array.isArray(value) ? value[0] : value) -const toInt = (value: unknown, fieldName: string, required = true): number | null => { - const normalized = normalizeScalar(value) - - if (normalized === undefined || normalized === null || normalized === '') { - if (required) { - throw createError({ statusCode: 400, statusMessage: `${fieldName} is required` }) - } - - return null - } - - const parsed = typeof normalized === 'number' ? normalized : Number(normalized) - - if (!Number.isInteger(parsed)) { - throw createError({ statusCode: 400, statusMessage: `${fieldName} must be an integer` }) - } - - return parsed -} - -const toDate = (value: unknown, fieldName: string, required = true): Date | null => { - const normalized = normalizeScalar(value) - - if (normalized === undefined || normalized === null || normalized === '') { - if (required) { - throw createError({ statusCode: 400, statusMessage: `${fieldName} is required` }) - } - - return null - } - - if (typeof normalized === 'string') { - const dateOnlyMatch = normalized.match(/^(\d{4})-(\d{2})-(\d{2})$/) - - if (dateOnlyMatch) { - const [, year, month, day] = dateOnlyMatch - const parsed = new Date(Number(year), Number(month) - 1, Number(day)) - - if (Number.isNaN(parsed.getTime())) { - throw createError({ statusCode: 400, statusMessage: `${fieldName} must be a valid date` }) - } - - return parsed - } - } - - const parsed = new Date(String(normalized)) - - if (Number.isNaN(parsed.getTime())) { - throw createError({ statusCode: 400, statusMessage: `${fieldName} must be a valid date` }) - } - - return parsed -} - -const toBoolean = (value: unknown) => { - const normalized = normalizeScalar(value) - - return normalized === true || normalized === 'true' || normalized === 1 || normalized === '1' -} - const toOptionalJson = (value: unknown) => { if (value === undefined) { return undefined @@ -369,42 +307,6 @@ export default defineEventHandler(async (event) => { } } - if (selectedAction === 'listForms') { - const query = getQuery(event) - const formGroupId = query.formGroup !== undefined ? toInt(query.formGroup, 'formGroup', false) : null - const weeklyDate = - !!query.weeklyDate - ? (toDate(query.weeklyDate, 'weeklyDate') as Date) - : null - - const where: Prisma.FormWhereInput = {} - - if (formGroupId !== null) { where.formGroup = formGroupId } - - if (weeklyDate) { - const matchingGroup = await findMatchingFormGroupByDate(weeklyDate) - - if (!matchingGroup) { - return [] - } - - where.formGroup = matchingGroup.id - where.startDate = matchingGroup.endDate - ? { gte: matchingGroup.startDate, lte: matchingGroup.endDate } - : { gte: matchingGroup.startDate } - } - - if (query.published) { where.published = toBoolean(query.published) } - - const forms = await prisma.form.findMany({ - where, - orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], - include: formInclude, - }) - - return forms.map((form) => mapForm(form, form.FormGroup.startDate)) - } - if (selectedAction === 'resolveFormGroupRangeByDate') { const weeklyDate = toDate(getQuery(event).weeklyDate, 'weeklyDate') as Date const matchingGroup = await findMatchingFormGroupByDate(weeklyDate) diff --git a/server/api/form/listForms.get.ts b/server/api/form/listForms.get.ts index b56e076..14e5337 100644 --- a/server/api/form/listForms.get.ts +++ b/server/api/form/listForms.get.ts @@ -1,9 +1,51 @@ -import { prisma } from '../../utils/prisma' +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' import { getQuery, createError } from 'h3' -import { requireAdmin, requireSession } from '../../utils/require-session' - +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' export default defineEventHandler(async (event) => { - + const query = getQuery(event) + //requireSessions + //finish coercion for query params + const formGroupId = z.int().safeParse(query.formGroupId).data + const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data + const published = z.boolean().safeParse(query.published).data + + const where: Prisma.FormWhereInput = {} + + if (formGroupId !== null) { where.formGroup = formGroupId } + + //split into two handlers? one for date matching an + if (weeklyDate) { + const matchingGroup = await findMatchingFormGroupByDate(weeklyDate) //matchGroupbyDate or whatever handler + + if (!matchingGroup) { + return [] + } + + where.formGroup = matchingGroup.id + where.startDate = matchingGroup.endDate + ? { gte: matchingGroup.startDate, lte: matchingGroup.endDate } + : { gte: matchingGroup.startDate } + } + + if (published) {where.published = published} + + const formInclude = { + Components: { + orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], + }, + FormGroup: true, + } + + const forms = await prisma.form.findMany({ + where, + orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], + include: formInclude, + }) + + return forms +}) - }) \ No newline at end of file + \ No newline at end of file diff --git a/server/api/form/testgetall.get.ts b/server/api/form/testgetall.get.ts new file mode 100644 index 0000000..281d2ff --- /dev/null +++ b/server/api/form/testgetall.get.ts @@ -0,0 +1,22 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' + +//delete handler once done with api handler output testing + +export default eventHandler(async (event) => { + + const formInclude = { + Components: { + orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], + }, + FormGroup: true, + } + + return prisma.form.findMany({ + orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], + include: formInclude, + }) +}) \ No newline at end of file diff --git a/server/api/formGroup/MatchGroupByDate.get.ts b/server/api/formGroup/MatchGroupByDate.get.ts new file mode 100644 index 0000000..87d37ac --- /dev/null +++ b/server/api/formGroup/MatchGroupByDate.get.ts @@ -0,0 +1,10 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' + +export default defineEventHandler(async (event) => { + + +}) \ No newline at end of file diff --git a/server/utils/schemas.ts b/server/utils/schemas.ts index 1350b7b..697931f 100644 --- a/server/utils/schemas.ts +++ b/server/utils/schemas.ts @@ -60,10 +60,6 @@ export const formCreateSchema = z.object({ title: z.string().min(1).max(250), }) -export const formGETSchema = formCreateSchema.extend({ - id: z.int() -}) - export const formComponentCreateSchema = z.object({ form: z.int(), order: z.int().min(0), @@ -94,7 +90,6 @@ export type StudentCreate = z.infer export type StudentUpdate = z.infer export type AnnouncementCreate = z.infer export type FormCreate = z.infer -export type FormGET = z.infer export type FormGroupGET = z.infer export type FormGroupCreate = z.infer export type FormComponentCreate = z.infer From ca13656fd418bd3f62123df1a5b647c273c15875 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Tue, 2 Jun 2026 11:12:22 -0500 Subject: [PATCH 03/18] match from group by date, prisma inclusions, and listforms action extrapolated --- server/api/form/index.ts | 19 ------------ .../form/{listForms.get.ts => list.get.ts} | 12 +++---- server/api/form/testgetall.get.ts | 1 - server/api/formGroup/MatchByDate.get.ts | 31 +++++++++++++++++++ server/api/formGroup/MatchGroupByDate.get.ts | 10 ------ .../{listFormGroups.get.ts => list.get.ts} | 0 server/utils/inclusions.ts | 19 ++++++++++++ 7 files changed, 54 insertions(+), 38 deletions(-) rename server/api/form/{listForms.get.ts => list.get.ts} (82%) create mode 100644 server/api/formGroup/MatchByDate.get.ts delete mode 100644 server/api/formGroup/MatchGroupByDate.get.ts rename server/api/formGroup/{listFormGroups.get.ts => list.get.ts} (100%) create mode 100644 server/utils/inclusions.ts diff --git a/server/api/form/index.ts b/server/api/form/index.ts index f8fd8af..7c9c918 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -240,25 +240,6 @@ const mapForm = ( } } -const formGroupInclude = { - RaffleWinner: true, - Forms: { - orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], - include: { - Components: { - orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], - }, - }, - }, -} - -const formInclude = { - Components: { - orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], - }, - FormGroup: true, -} - export default defineEventHandler(async (event) => { const method = event.node.req.method ?? 'GET' const body = method === 'GET' ? null : ((await readBody(event).catch(() => null)) as Record | null) diff --git a/server/api/form/listForms.get.ts b/server/api/form/list.get.ts similarity index 82% rename from server/api/form/listForms.get.ts rename to server/api/form/list.get.ts index 14e5337..9c59689 100644 --- a/server/api/form/listForms.get.ts +++ b/server/api/form/list.get.ts @@ -3,6 +3,7 @@ import { Prisma } from '~~/prisma/generated/client' import { getQuery, createError } from 'h3' import { requireAdmin, requireSession } from '../../utils/require-session' import z from 'zod' +import { formInclude } from '../../utils/inclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) @@ -18,7 +19,9 @@ export default defineEventHandler(async (event) => { //split into two handlers? one for date matching an if (weeklyDate) { - const matchingGroup = await findMatchingFormGroupByDate(weeklyDate) //matchGroupbyDate or whatever handler + const matchingGroup = await $fetch('/api/formGroup/MatchByDate', { + query: { date: weeklyDate.toISOString() } + }) if (!matchingGroup) { return [] @@ -32,13 +35,6 @@ export default defineEventHandler(async (event) => { if (published) {where.published = published} - const formInclude = { - Components: { - orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], - }, - FormGroup: true, - } - const forms = await prisma.form.findMany({ where, orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], diff --git a/server/api/form/testgetall.get.ts b/server/api/form/testgetall.get.ts index 281d2ff..bbeb563 100644 --- a/server/api/form/testgetall.get.ts +++ b/server/api/form/testgetall.get.ts @@ -5,7 +5,6 @@ import { requireAdmin, requireSession } from '../../utils/require-session' import z from 'zod' //delete handler once done with api handler output testing - export default eventHandler(async (event) => { const formInclude = { diff --git a/server/api/formGroup/MatchByDate.get.ts b/server/api/formGroup/MatchByDate.get.ts new file mode 100644 index 0000000..be62915 --- /dev/null +++ b/server/api/formGroup/MatchByDate.get.ts @@ -0,0 +1,31 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' + +export default defineEventHandler(async (event) => { + //session check needed + const query = getQuery(event) + const date = z.coerce.date().safeParse(query.date).data + + if (!date) { + throw createError({ + statusCode: 400, + message: 'Invalid or Missing Date', + }) + } + + return await prisma.formGroup.findFirst({ + where: { + startDate: { lte: date }, + OR: [ + { endDate: null }, + { endDate: { gte: date } }, + ], + }, + orderBy: [{ startDate: 'desc' }, { id: 'desc' }], + select: { id: true, startDate: true, endDate: true }, + }) + +}) \ No newline at end of file diff --git a/server/api/formGroup/MatchGroupByDate.get.ts b/server/api/formGroup/MatchGroupByDate.get.ts deleted file mode 100644 index 87d37ac..0000000 --- a/server/api/formGroup/MatchGroupByDate.get.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { prisma } from '../../utils/prisma' -import { Prisma } from '~~/prisma/generated/client' -import { getQuery, createError } from 'h3' -import { requireAdmin, requireSession } from '../../utils/require-session' -import z from 'zod' - -export default defineEventHandler(async (event) => { - - -}) \ No newline at end of file diff --git a/server/api/formGroup/listFormGroups.get.ts b/server/api/formGroup/list.get.ts similarity index 100% rename from server/api/formGroup/listFormGroups.get.ts rename to server/api/formGroup/list.get.ts diff --git a/server/utils/inclusions.ts b/server/utils/inclusions.ts new file mode 100644 index 0000000..84608a8 --- /dev/null +++ b/server/utils/inclusions.ts @@ -0,0 +1,19 @@ +//Commonly Used Inclusions for prisma queries +export const formInclude = { + Components: { + orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], + }, + FormGroup: true, +} + +export const formGroupInclude = { + RaffleWinner: true, + Forms: { + orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], + include: { + Components: { + orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], + }, + }, + }, +} From 07b97b52186e20789fc34ce106da2d37ba59a275 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Tue, 2 Jun 2026 12:10:07 -0500 Subject: [PATCH 04/18] useAdmin updated for listForms, preupdate for other composables --- app/composables/useAdmin.ts | 11 +++++------ app/composables/useCurrentFormGroup.ts | 1 + app/composables/useRaffleSpin.ts | 2 ++ server/api/form/index.ts | 14 -------------- server/api/form/list.get.ts | 2 +- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index 9d183ef..8e9c815 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -81,7 +81,8 @@ export const useAdmin = () => { } const mapApiFormToUi = (form: any) => { - const questionList = Array.isArray(form.questions) ? form.questions : [] + const questionList = Array.isArray(form.Components) ? form.Components : [] + questionList.slice().sort((left: any, right: any) => left.order - right.order) //replace direct type casting with better alternative, maybe zod coercing? return { @@ -91,7 +92,7 @@ export const useAdmin = () => { title: form.title, date: formatDate(form.startDate || ''), //update to dayjs format function status: form.published ? 'Active' : 'Unpublished', - questions: form.Components.slice().sort((left, right) => left.order - right.order).map((question: any) => ({ + questions: questionList.map((question: any) => ({ id: question.id, type: question.questionType || 'text', text: question.questionText || '', @@ -221,10 +222,8 @@ export const useAdmin = () => { const loadPublishedForms = async () => { try { - const forms = await callFormApi('GET', { - action: 'listForms', - weeklyDate: historyWeekStart.value || undefined, - }) + const forms = await $fetch('/api/form/list', + { query: { weeklyDate: historyWeekStart.value || undefined} }) publishedForms.value = (forms ?? []).map(mapApiFormToUi) } catch (error) { console.error('Failed to load forms', error) diff --git a/app/composables/useCurrentFormGroup.ts b/app/composables/useCurrentFormGroup.ts index b3c2974..8c4e198 100644 --- a/app/composables/useCurrentFormGroup.ts +++ b/app/composables/useCurrentFormGroup.ts @@ -36,6 +36,7 @@ export const useCurrentFormGroup = () => { FormGroup.value.activeFormGroup = activeFg try { + //switch to form list handler with published = true const formsAPIResponse = await $fetch('/api/form', { query: { action: 'getOnlyActiveFormsinGroup', formGroup: activeFg.id } }) diff --git a/app/composables/useRaffleSpin.ts b/app/composables/useRaffleSpin.ts index 041c80f..4e5a64c 100644 --- a/app/composables/useRaffleSpin.ts +++ b/app/composables/useRaffleSpin.ts @@ -9,6 +9,7 @@ export const useRaffleSpin = () => { const raffleWinner = ref(null) const spinCount = ref(0) + //match form group by date, raffle winner include added const loadRaffleFormGroup = async () => { const val = raffleWeekStart.value const dateStr = val instanceof Date ? val.toISOString().split('T')[0] : String(val).split('T')[0] @@ -16,6 +17,7 @@ export const useRaffleSpin = () => { raffleFormGroup.value = data || null } + //wtf is it even calling const loadRaffleForms = async () => { if (!raffleFormGroup.value) { raffleForms.value = [] diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 7c9c918..48b4565 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -115,20 +115,6 @@ const findOrCreateWeeklyFormGroup = async (date: Date) => { return createdGroup.id } -const findMatchingFormGroupByDate = async (date: Date) => { - return await prisma.formGroup.findFirst({ - where: { - startDate: { lte: date }, - OR: [ - { endDate: null }, - { endDate: { gte: date } }, - ], - }, - orderBy: [{ startDate: 'desc' }, { id: 'desc' }], - select: { id: true, startDate: true, endDate: true }, - }) -} - const getAction = (event: H3Event, body: Record | null) => { const query = getQuery(event) return (query.action ?? body?.action) as ActionName | undefined diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index 9c59689..501d2e3 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -19,7 +19,7 @@ export default defineEventHandler(async (event) => { //split into two handlers? one for date matching an if (weeklyDate) { - const matchingGroup = await $fetch('/api/formGroup/MatchByDate', { + const matchingGroup = await $fetch('/api/formGroup/matchByDate', { query: { date: weeklyDate.toISOString() } }) From 359d688c4a4e0879296d1df0701f73cf2b4bbc04 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Tue, 2 Jun 2026 15:14:36 -0500 Subject: [PATCH 05/18] removing tests, fixing +consolidating handlers --- server/api/form/index.ts | 5 +++-- server/api/form/list.get.ts | 20 +++++++++++++------- server/api/form/listtest.get.ts | 20 ++++++++++++++++++++ server/api/form/testgetall.get.ts | 21 --------------------- 4 files changed, 36 insertions(+), 30 deletions(-) create mode 100644 server/api/form/listtest.get.ts delete mode 100644 server/api/form/testgetall.get.ts diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 48b4565..7d98ae4 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -2,6 +2,7 @@ import { Prisma } from '~~/prisma/generated/client' import { auth } from '~~/server/utils/auth' import { prisma } from '~~/server/utils/prisma' import { getQuery, setResponseStatus, type H3Event } from 'h3' +import {z } from 'zod' type ActionName = | 'listFormGroups' @@ -334,9 +335,9 @@ export default defineEventHandler(async (event) => { const query = getQuery(event) const where: Prisma.FormWhereInput = {} - if (query.formGroup !== null) { where.formGroup = Number(query.formGroup) } + if (query.formGroup) { where.formGroup = Number(query.formGroup) } - if (query.published) { where.published = toBoolean(query.published) } + if (query.published) { where.published = z.coerce.boolean().parse(query.published) } return await prisma.form.findMany({ where, diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index 501d2e3..50761ac 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -8,17 +8,24 @@ import { formInclude } from '../../utils/inclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) //requireSessions - //finish coercion for query params - const formGroupId = z.int().safeParse(query.formGroupId).data + //fix type casting + const formGroupId = z.coerce.number().safeParse(query.formGroupId).data const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data - const published = z.boolean().safeParse(query.published).data + const published = query.published?.toString const where: Prisma.FormWhereInput = {} - if (formGroupId !== null) { where.formGroup = formGroupId } + console.log("formGroupId after filtering", formGroupId) + console.log("\nweeklyDate after filtering", weeklyDate) + console.log("\npublished after filtering", published) - //split into two handlers? one for date matching an - if (weeklyDate) { + //if (published) {where.published = published} + + if (formGroupId) { + console.log('Filtering forms by formGroupId:', formGroupId) + where.formGroup = formGroupId} + + else if (weeklyDate) { const matchingGroup = await $fetch('/api/formGroup/matchByDate', { query: { date: weeklyDate.toISOString() } }) @@ -33,7 +40,6 @@ export default defineEventHandler(async (event) => { : { gte: matchingGroup.startDate } } - if (published) {where.published = published} const forms = await prisma.form.findMany({ where, diff --git a/server/api/form/listtest.get.ts b/server/api/form/listtest.get.ts new file mode 100644 index 0000000..be54bc3 --- /dev/null +++ b/server/api/form/listtest.get.ts @@ -0,0 +1,20 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' +import { formInclude } from '../../utils/inclusions' + + +export default defineEventHandler(async (event) => { + const query = getQuery(event) + const where: Prisma.FormWhereInput = {} + + if (query.formGroup) { where.formGroup = Number(query.formGroup) } + + if (query.published) { where.published = z.coerce.boolean().parse(query.published) } + return await prisma.form.findMany({ + where, + orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], + }) +}) \ No newline at end of file diff --git a/server/api/form/testgetall.get.ts b/server/api/form/testgetall.get.ts deleted file mode 100644 index bbeb563..0000000 --- a/server/api/form/testgetall.get.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { prisma } from '../../utils/prisma' -import { Prisma } from '~~/prisma/generated/client' -import { getQuery, createError } from 'h3' -import { requireAdmin, requireSession } from '../../utils/require-session' -import z from 'zod' - -//delete handler once done with api handler output testing -export default eventHandler(async (event) => { - - const formInclude = { - Components: { - orderBy: [{ order: 'asc' as const }, { id: 'asc' as const }], - }, - FormGroup: true, - } - - return prisma.form.findMany({ - orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], - include: formInclude, - }) -}) \ No newline at end of file From bc3a57a8ec5200f7e42ffd454e905edd934c0fa4 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Wed, 3 Jun 2026 21:49:24 -0500 Subject: [PATCH 06/18] test handler gone + index.ts trimming --- server/api/form/index.ts | 43 --------------------------------- server/api/form/list.get.ts | 12 +++------ server/api/form/listtest.get.ts | 20 --------------- 3 files changed, 4 insertions(+), 71 deletions(-) delete mode 100644 server/api/form/listtest.get.ts diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 7d98ae4..73c4d7f 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -124,46 +124,6 @@ const getAction = (event: H3Event, body: Record | null) => { const isFormApiDevBypassEnabled = () => process.env.NODE_ENV !== 'production' || process.env.FORM_API_DEV_BYPASS === 'true' -const requireAdminSession = async (event: H3Event) => { - const session = await auth.api.getSession({ - headers: event.headers, - }) - - if (!session) { - if (isFormApiDevBypassEnabled()) { - return { session: null, userId: null, admin: null, bypassed: true } - } - - throw createError({ - statusCode: 401, - statusMessage: 'Unauthorized: no active session. Log in, or set FORM_API_DEV_BYPASS=true for local testing only.', - }) - } - - const userId = normalizeScalar(session.user.id) - - if (!userId || typeof userId !== 'string') { - throw createError({ statusCode: 400, statusMessage: 'Invalid session user id' }) - } - - const admin = await prisma.admin.findUnique({ - where: { userId }, - }) - - if (!admin) { - if (isFormApiDevBypassEnabled()) { - return { session, userId, admin: null, bypassed: true } - } - - throw createError({ - statusCode: 403, - statusMessage: 'Forbidden: current user is not an admin.', - }) - } - - return { session, userId, admin, bypassed: false } -} - const mapComponent = (component: { id: number form: number @@ -350,9 +310,6 @@ export default defineEventHandler(async (event) => { } - - const { admin } = await requireAdminSession(event) - if (method === 'POST') { if (action === 'createFormGroup') { const startDate = toDate(body?.startDate, 'startDate') diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index 50761ac..56087df 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -8,22 +8,18 @@ import { formInclude } from '../../utils/inclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) //requireSessions - //fix type casting + //error handling const formGroupId = z.coerce.number().safeParse(query.formGroupId).data const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data - const published = query.published?.toString + const published = z.boolean().safeParse(query.published).data const where: Prisma.FormWhereInput = {} - console.log("formGroupId after filtering", formGroupId) - console.log("\nweeklyDate after filtering", weeklyDate) console.log("\npublished after filtering", published) - //if (published) {where.published = published} + if (published !== undefined) {where.published = published} - if (formGroupId) { - console.log('Filtering forms by formGroupId:', formGroupId) - where.formGroup = formGroupId} + if (formGroupId) {where.formGroup = formGroupId} else if (weeklyDate) { const matchingGroup = await $fetch('/api/formGroup/matchByDate', { diff --git a/server/api/form/listtest.get.ts b/server/api/form/listtest.get.ts deleted file mode 100644 index be54bc3..0000000 --- a/server/api/form/listtest.get.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { prisma } from '../../utils/prisma' -import { Prisma } from '~~/prisma/generated/client' -import { getQuery, createError } from 'h3' -import { requireAdmin, requireSession } from '../../utils/require-session' -import z from 'zod' -import { formInclude } from '../../utils/inclusions' - - -export default defineEventHandler(async (event) => { - const query = getQuery(event) - const where: Prisma.FormWhereInput = {} - - if (query.formGroup) { where.formGroup = Number(query.formGroup) } - - if (query.published) { where.published = z.coerce.boolean().parse(query.published) } - return await prisma.form.findMany({ - where, - orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], - }) -}) \ No newline at end of file From 8eb26b0fdbea64bffabfb28a48dc221bcc4ccabb Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 4 Jun 2026 13:52:12 -0500 Subject: [PATCH 07/18] form put handler split --- server/api/form/[id].put.ts | 23 ++++++++++++++ server/api/form/index.ts | 60 ------------------------------------- server/utils/schemas.ts | 17 +++++++---- 3 files changed, 34 insertions(+), 66 deletions(-) create mode 100644 server/api/form/[id].put.ts diff --git a/server/api/form/[id].put.ts b/server/api/form/[id].put.ts new file mode 100644 index 0000000..61c8a7d --- /dev/null +++ b/server/api/form/[id].put.ts @@ -0,0 +1,23 @@ +import { Prisma } from '~~/prisma/generated/client' +import { auth } from '~~/server/utils/auth' +import { prisma } from '~~/server/utils/prisma' +import { getQuery, setResponseStatus, type H3Event } from 'h3' +import { formUpdateSchema } from '~~/server/utils/schemas' +import { z } from 'zod' + +export default eventHandler(async (event) => { + + //require sessions + const body = formUpdateSchema.safeParse(await readBody(event)) + + if (!body.success) {throw createError({ statusCode: 400, message: body.error.message })} + + const id = z.coerce.number().safeParse(event.context.params?.id) + + if (!id.success || !id.data) {throw createError({ statusCode: 400, message: 'Missing or Invalid form ID'})} + + return await prisma.form.update({ + where: { id: id.data }, + data: body.data + }) + }) \ No newline at end of file diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 73c4d7f..f2bff73 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -14,7 +14,6 @@ type ActionName = | 'createForm' | 'createComponent' | 'updateFormGroup' - | 'updateForm' | 'updateComponent' | 'deleteFormGroup' | 'deleteForm' @@ -514,65 +513,6 @@ export default defineEventHandler(async (event) => { } } - if (action === 'updateForm') { - const id = toInt(body?.id, 'id') - const data: { - formGroup?: number - startDate?: Date - endDate?: Date | null - published?: boolean - order?: number - title?: string | null - } = {} - - if (hasOwnField(body, 'formGroup')) { - const formGroup = toInt(body?.formGroup, 'formGroup') - const group = await prisma.formGroup.findUnique({ - where: { id: formGroup as number }, - select: { id: true }, - }) - - if (!group) { - throw createError({ statusCode: 404, statusMessage: 'Form group not found' }) - } - - data.formGroup = formGroup as number - } - - if (hasOwnField(body, 'startDate')) { - data.startDate = toDate(body?.startDate, 'startDate') as Date - } - - if (hasOwnField(body, 'endDate')) { - data.endDate = toDate(body?.endDate, 'endDate', false) - } - - if (hasOwnField(body, 'published')) { - data.published = toBoolean(body?.published) - } - - if (hasOwnField(body, 'order')) { - data.order = toInt(body?.order, 'order') as number - } - - if (hasOwnField(body, 'title')) { - const title = String(body?.title ?? '').trim() - data.title = title || null - } - - const updated = await prisma.form.update({ - where: { id: id as number }, - data: data as Prisma.FormUncheckedUpdateInput, - include: formInclude, - }) - - return { - success: true, - message: 'Form updated', - data: mapForm(updated, updated.FormGroup.startDate), - } - } - if (action === 'updateComponent') { const id = toInt(body?.id, 'id') const data: Prisma.FormComponentUpdateInput = {} diff --git a/server/utils/schemas.ts b/server/utils/schemas.ts index 697931f..6520b80 100644 --- a/server/utils/schemas.ts +++ b/server/utils/schemas.ts @@ -45,11 +45,6 @@ export const formGroupCreateSchema = z.object({ endDate: z.coerce.date(), }) -export const formGroupGETSchema = formGroupCreateSchema.extend({ - id: z.int(), - raffleWinner: z.int().nullish() -}) - export const formCreateSchema = z.object({ order: z.int(), startDate: z.coerce.date(), @@ -60,6 +55,16 @@ export const formCreateSchema = z.object({ title: z.string().min(1).max(250), }) +export const formUpdateSchema = z.object({ + order: z.int().optional(), + startDate: z.coerce.date().optional(), + endDate: z.coerce.date().nullish(), + published: z.boolean().optional(), + author: z.cuid2().nullish(), + formGroup: z.int().optional(), + title: z.string().min(1).max(250).optional(), +}) + export const formComponentCreateSchema = z.object({ form: z.int(), order: z.int().min(0), @@ -90,7 +95,7 @@ export type StudentCreate = z.infer export type StudentUpdate = z.infer export type AnnouncementCreate = z.infer export type FormCreate = z.infer -export type FormGroupGET = z.infer +export type FormUpdate = z.infer export type FormGroupCreate = z.infer export type FormComponentCreate = z.infer export type FormSubmissionCreate = z.infer From 7141cac2e2a29a62a4d37b5a184b7dfe84c58410 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 4 Jun 2026 14:07:12 -0500 Subject: [PATCH 08/18] admin.ts put form endpoint updated --- app/composables/useAdmin.ts | 13 +++++++------ server/api/form/list.get.ts | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index 8e9c815..a6de6aa 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -324,12 +324,13 @@ export const useAdmin = () => { if (!startDate) throw new Error('Invalid week start date') startDate.setDate(startDate.getDate() + dayIndex) - await callFormApi('PUT', {}, { - action: 'updateForm', - id: editingFormId.value, - startDate: formatYmdLocal(startDate), - published: true, - title: formTitle.value, + await useFetch(`/api/form/${editingFormId.value}`, { + method: 'PUT', + body: { + startDate: formatYmdLocal(startDate), //swap to dayjs + published: true, //this is what it was prev, where is the value for editing form published value ? + title: formTitle.value, + } }) const existingForm = publishedForms.value.find((form) => Number(form.id) === Number(editingFormId.value)) diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index 56087df..2d23c72 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -8,7 +8,7 @@ import { formInclude } from '../../utils/inclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) //requireSessions - //error handling + //add error handling const formGroupId = z.coerce.number().safeParse(query.formGroupId).data const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data const published = z.boolean().safeParse(query.published).data From 885dc0a5378003ceabe5a3d28fe6d3cd072358a4 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 4 Jun 2026 14:20:40 -0500 Subject: [PATCH 09/18] removed unused deleteForm action --- app/composables/useAdmin.ts | 2 +- server/api/form/index.ts | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index a6de6aa..e044455 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -328,7 +328,7 @@ export const useAdmin = () => { method: 'PUT', body: { startDate: formatYmdLocal(startDate), //swap to dayjs - published: true, //this is what it was prev, where is the value for editing form published value ? + published: true, title: formTitle.value, } }) diff --git a/server/api/form/index.ts b/server/api/form/index.ts index f2bff73..e936911 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -16,7 +16,6 @@ type ActionName = | 'updateFormGroup' | 'updateComponent' | 'deleteFormGroup' - | 'deleteForm' | 'deleteComponent' const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] @@ -590,19 +589,6 @@ export default defineEventHandler(async (event) => { } } - if (action === 'deleteForm') { - const id = toInt(body?.id ?? getQuery(event).id, 'id') - - await prisma.form.delete({ - where: { id: id as number }, - }) - - return { - success: true, - message: 'Form deleted', - } - } - if (action === 'deleteComponent') { const id = toInt(body?.id ?? getQuery(event).id, 'id') From 5253f7dbe60364bbdca825967ddd13c916b6645f Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 4 Jun 2026 14:46:41 -0500 Subject: [PATCH 10/18] fixed boolean coercion issue for form/list.get --- server/api/form/list.get.ts | 21 ++++++++++--------- .../{inclusions.ts => prismaInclusions.ts} | 0 2 files changed, 11 insertions(+), 10 deletions(-) rename server/utils/{inclusions.ts => prismaInclusions.ts} (100%) diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index 2d23c72..e173286 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -3,25 +3,27 @@ import { Prisma } from '~~/prisma/generated/client' import { getQuery, createError } from 'h3' import { requireAdmin, requireSession } from '../../utils/require-session' import z from 'zod' -import { formInclude } from '../../utils/inclusions' +import { formInclude } from '../../utils/prismaInclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) - //requireSessions - //add error handling + //add requireSessions const formGroupId = z.coerce.number().safeParse(query.formGroupId).data const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data - const published = z.boolean().safeParse(query.published).data - + const where: Prisma.FormWhereInput = {} - console.log("\npublished after filtering", published) - - if (published !== undefined) {where.published = published} + //expecting 0/1 for t/f + if (query.published) { + const pre = z.coerce.number().safeParse(query.published) + if (!pre.success) { throw createError({ statusCode: 400, message: 'Invalid published value' })} + const published = z.coerce.boolean().safeParse(pre.data).data + where.published = published + } if (formGroupId) {where.formGroup = formGroupId} - else if (weeklyDate) { + if (weeklyDate) { const matchingGroup = await $fetch('/api/formGroup/matchByDate', { query: { date: weeklyDate.toISOString() } }) @@ -36,7 +38,6 @@ export default defineEventHandler(async (event) => { : { gte: matchingGroup.startDate } } - const forms = await prisma.form.findMany({ where, orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], diff --git a/server/utils/inclusions.ts b/server/utils/prismaInclusions.ts similarity index 100% rename from server/utils/inclusions.ts rename to server/utils/prismaInclusions.ts From 97b8f5e561b60006f7437f042fc7de70ccfa89ff Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Wed, 10 Jun 2026 13:47:58 -0500 Subject: [PATCH 11/18] listFormGroups, getOnlyActiveForms, getFormGroup split --- app/composables/useCurrentFormGroup.ts | 39 +++++++---------- app/composables/useRaffleSpin.ts | 2 + server/api/form/[id].put.ts | 2 +- server/api/form/index.ts | 56 ------------------------- server/api/form/list.get.ts | 10 +++-- server/api/formGroup/MatchByDate.get.ts | 2 +- server/api/formGroup/[id].get.ts | 30 +++++++++++++ server/api/formGroup/[id].ts | 0 server/api/formGroup/list.get.ts | 24 +++++++++++ 9 files changed, 80 insertions(+), 85 deletions(-) create mode 100644 server/api/formGroup/[id].get.ts delete mode 100644 server/api/formGroup/[id].ts diff --git a/app/composables/useCurrentFormGroup.ts b/app/composables/useCurrentFormGroup.ts index 8c4e198..ba11d2c 100644 --- a/app/composables/useCurrentFormGroup.ts +++ b/app/composables/useCurrentFormGroup.ts @@ -1,4 +1,5 @@ import type { FormGroup, Form, FormComponent } from '~~/prisma/generated/client' +import dayjs from 'dayjs' export type CurrentFormGroupState = { activeFormGroup: FormGroup | null @@ -13,41 +14,34 @@ export const useCurrentFormGroup = () => { formComponents: {} })) - const loadFormComponents = async (formId: number) => { - try { - const componentsAPIResponse = await $fetch('/api/formComponent', { - query: { form: formId } - }) - FormGroup.value.formComponents[formId] = Array.isArray(componentsAPIResponse) ? componentsAPIResponse : [] - } catch (error) { - console.error(`Failed to load form components for form ${formId}:`, error) - FormGroup.value.formComponents[formId] = [] - } - } - const loadActiveFormGroup = async () => { try { const formGroupAPIResponse = await $fetch('/api/formGroup?active=true') - // Handle if the API returns an array or single item const activeFg = Array.isArray(formGroupAPIResponse) ? formGroupAPIResponse[0] : formGroupAPIResponse if (activeFg) { FormGroup.value.activeFormGroup = activeFg try { - //switch to form list handler with published = true - const formsAPIResponse = await $fetch('/api/form', { - query: { action: 'getOnlyActiveFormsinGroup', formGroup: activeFg.id } + const formsAPIResponse = await $fetch('/api/form/list', { + query: { published: 1, formGroup: activeFg.id } }) - FormGroup.value.forms = Array.isArray(formsAPIResponse) ? formsAPIResponse : [] - - // Load form components for each form in parallel FormGroup.value.formComponents = {} - await Promise.all( - FormGroup.value.forms.map(form => loadFormComponents(form.id)) - ) + + FormGroup.value.forms = formsAPIResponse.map((form) => { + const {Components, FormGroup: _removedFormGroupField, startDate, endDate, ...restOfForm} = form + + FormGroup.value.formComponents[form.id] = Components ?? [] + + return { + ...restOfForm, + startDate: dayjs(startDate).toDate(), + endDate: endDate ? dayjs(endDate).toDate() : null, + } + }) + } catch (error) { console.error('Failed to load forms for active form group:', error) FormGroup.value.forms = [] @@ -71,7 +65,6 @@ export const useCurrentFormGroup = () => { return { FormGroup, loadActiveFormGroup, - loadFormComponents, totalFormsInGroup } } \ No newline at end of file diff --git a/app/composables/useRaffleSpin.ts b/app/composables/useRaffleSpin.ts index 4e5a64c..6b0e635 100644 --- a/app/composables/useRaffleSpin.ts +++ b/app/composables/useRaffleSpin.ts @@ -13,6 +13,7 @@ export const useRaffleSpin = () => { const loadRaffleFormGroup = async () => { const val = raffleWeekStart.value const dateStr = val instanceof Date ? val.toISOString().split('T')[0] : String(val).split('T')[0] + //merge formGroup/list.ts with index.ts const data = await $fetch(`/api/formGroup?date=${dateStr}`, { method: 'GET' }) raffleFormGroup.value = data || null } @@ -61,6 +62,7 @@ export const useRaffleSpin = () => { const studentId = winningSubmission.student try { + //make formGroup/index.put.ts await $fetch(`/api/formGroup`, { method: 'PUT', body: { diff --git a/server/api/form/[id].put.ts b/server/api/form/[id].put.ts index 61c8a7d..dbc40fa 100644 --- a/server/api/form/[id].put.ts +++ b/server/api/form/[id].put.ts @@ -12,7 +12,7 @@ export default eventHandler(async (event) => { if (!body.success) {throw createError({ statusCode: 400, message: body.error.message })} - const id = z.coerce.number().safeParse(event.context.params?.id) + const id = z.number().safeParse(event.context.params?.id) if (!id.success || !id.data) {throw createError({ statusCode: 400, message: 'Missing or Invalid form ID'})} diff --git a/server/api/form/index.ts b/server/api/form/index.ts index e936911..bdbeb56 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -5,9 +5,6 @@ import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' type ActionName = - | 'listFormGroups' - | 'getOnlyActiveFormsinGroup' - | 'getFormGroup' | 'resolveFormGroupRangeByDate' | 'getFormGroupSubmissions' | 'createFormGroup' @@ -195,44 +192,6 @@ export default defineEventHandler(async (event) => { if (method === 'GET') { const selectedAction = action ?? 'listFormGroups' - if (selectedAction === 'listFormGroups') { - const groups = await prisma.formGroup.findMany({ - orderBy: [{ startDate: 'desc' }, { id: 'desc' }], - include: formGroupInclude, - }) - - return groups.map((group) => ({ - id: group.id, - startDate: formatIsoDate(group.startDate), - endDate: formatIsoDate(group.endDate), - raffleWinner: group.raffleWinner, - raffleWinnerStudent: group.RaffleWinner, - forms: group.Forms.map((form) => mapForm(form, group.startDate)), - })) - } - - if (selectedAction === 'getFormGroup') { - const groupId = toInt(getQuery(event).id, 'id') - - const group = await prisma.formGroup.findUnique({ - where: { id: groupId as number }, - include: formGroupInclude, - }) - - if (!group) { - throw createError({ statusCode: 404, statusMessage: 'Form group not found' }) - } - - return { - id: group.id, - startDate: formatIsoDate(group.startDate), - endDate: formatIsoDate(group.endDate), - raffleWinner: group.raffleWinner, - raffleWinnerStudent: group.RaffleWinner, - forms: group.Forms.map((form) => mapForm(form, group.startDate)), - } - } - if (selectedAction === 'resolveFormGroupRangeByDate') { const weeklyDate = toDate(getQuery(event).weeklyDate, 'weeklyDate') as Date const matchingGroup = await findMatchingFormGroupByDate(weeklyDate) @@ -289,21 +248,6 @@ export default defineEventHandler(async (event) => { } } - if (selectedAction === 'getOnlyActiveFormsinGroup') { - const query = getQuery(event) - const where: Prisma.FormWhereInput = {} - - if (query.formGroup) { where.formGroup = Number(query.formGroup) } - - if (query.published) { where.published = z.coerce.boolean().parse(query.published) } - - return await prisma.form.findMany({ - where, - orderBy: [{ formGroup: 'asc' }, { order: 'asc' }, { id: 'asc' }], - }) - } - - throw createError({ statusCode: 400, statusMessage: 'Unknown action' }) } diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index e173286..f3f35b4 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -8,14 +8,14 @@ import { formInclude } from '../../utils/prismaInclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) //add requireSessions - const formGroupId = z.coerce.number().safeParse(query.formGroupId).data - const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data + const formGroupId = z.number().safeParse(query.formGroupId).data + const weeklyDate = z.date().safeParse(query.weeklyDate).data const where: Prisma.FormWhereInput = {} - //expecting 0/1 for t/f + //expecting 0/1 for f/t if (query.published) { - const pre = z.coerce.number().safeParse(query.published) + const pre = z.number().safeParse(query.published) if (!pre.success) { throw createError({ statusCode: 400, message: 'Invalid published value' })} const published = z.coerce.boolean().safeParse(pre.data).data where.published = published @@ -23,6 +23,8 @@ export default defineEventHandler(async (event) => { if (formGroupId) {where.formGroup = formGroupId} + //warning/error received on nested fetch 'Invalid lazy handler result. It should be a function:\n' + //remove nested handling, use where.FormGroup if (weeklyDate) { const matchingGroup = await $fetch('/api/formGroup/matchByDate', { query: { date: weeklyDate.toISOString() } diff --git a/server/api/formGroup/MatchByDate.get.ts b/server/api/formGroup/MatchByDate.get.ts index be62915..7702d57 100644 --- a/server/api/formGroup/MatchByDate.get.ts +++ b/server/api/formGroup/MatchByDate.get.ts @@ -7,7 +7,7 @@ import z from 'zod' export default defineEventHandler(async (event) => { //session check needed const query = getQuery(event) - const date = z.coerce.date().safeParse(query.date).data + const date = z.date().safeParse(query.date).data if (!date) { throw createError({ diff --git a/server/api/formGroup/[id].get.ts b/server/api/formGroup/[id].get.ts new file mode 100644 index 0000000..269bf8a --- /dev/null +++ b/server/api/formGroup/[id].get.ts @@ -0,0 +1,30 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' +import dayjs from 'dayjs' +import { formGroupInclude } from '../../utils/prismaInclusions' + +export default defineEventHandler(async (event) => { + //add auth + const groupId = z.number().safeParse(event.context.params?.id).data + + const group = await prisma.formGroup.findUnique({ + where: { id: groupId as number }, + include: formGroupInclude, + }) + + if (!group) { + throw createError({ statusCode: 404, statusMessage: 'Form group not found' }) + } + + return { + id: group.id, + startDate: dayjs(group.startDate).toISOString(), + endDate: dayjs(group.endDate).toISOString(), + raffleWinner: group.raffleWinner, + raffleWinnerStudent: group.RaffleWinner, + forms: group.Forms, + } +}) \ No newline at end of file diff --git a/server/api/formGroup/[id].ts b/server/api/formGroup/[id].ts deleted file mode 100644 index e69de29..0000000 diff --git a/server/api/formGroup/list.get.ts b/server/api/formGroup/list.get.ts index e69de29..6f317fe 100644 --- a/server/api/formGroup/list.get.ts +++ b/server/api/formGroup/list.get.ts @@ -0,0 +1,24 @@ +import { Prisma } from '~~/prisma/generated/client' +import { auth } from '~~/server/utils/auth' +import { prisma } from '~~/server/utils/prisma' +import { getQuery, setResponseStatus, type H3Event } from 'h3' +import {z } from 'zod' +import { formGroupInclude } from '../../utils/prismaInclusions' + + +export default eventHandler(async (event: H3Event) => { + //add auth + const groups = await prisma.formGroup.findMany({ + orderBy: [{ startDate: 'desc' }, { id: 'desc' }], + include: formGroupInclude + }) + + return groups.map((group) => ({ + id: group.id, + startDate: group.startDate, + endDate: group.endDate, + raffleWinner: group.raffleWinner, + raffleWinnerStudent: group.RaffleWinner, + forms: group.Forms, + })) +}) \ No newline at end of file From 7aef679fdbb0633b12b8e9559a2f3ad86986a080 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Wed, 10 Jun 2026 15:08:57 -0500 Subject: [PATCH 12/18] ResolveDateByRange + other fixes --- app/composables/useAdmin.ts | 17 ++++++-------- app/composables/useCurrentFormGroup.ts | 30 ++++++++++++++----------- server/api/form/index.ts | 22 ------------------ server/api/form/list.get.ts | 7 +++--- server/api/formGroup/MatchByDate.get.ts | 21 +++++++++++------ server/api/formGroup/[id].get.ts | 19 ++++++++++++++-- 6 files changed, 59 insertions(+), 57 deletions(-) diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index e044455..4955604 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -241,23 +241,20 @@ export const useAdmin = () => { const fallbackWeekEnd = dayjs.utc(fallbackWeekStart).add(6, 'day').format('YYYY-MM-DD') try { - const result = await callFormApi<{ - found: boolean - startDate: string | null - endDate: string | null - }>('GET', { - action: 'resolveFormGroupRangeByDate', - weeklyDate: historyWeekStart.value, + const result = await useFetch('/api/formGroup/matchByDate', { + method: 'GET', + query: {date: historyWeekStart.value} }) - if (!result?.found) { + if (!result.data.value) { historyGroupStartDate.value = fallbackWeekStart historyGroupEndDate.value = fallbackWeekEnd return } + result.data.value - historyGroupStartDate.value = parseDateToYmd(result.startDate || '') - historyGroupEndDate.value = parseDateToYmd(result.endDate || '') || fallbackWeekEnd + historyGroupStartDate.value = parseDateToYmd(result.data.value.startDate || '') + historyGroupEndDate.value = parseDateToYmd(result.data.value.endDate || '') || fallbackWeekEnd } catch (error) { console.error('Failed to resolve form group range', error) historyGroupStartDate.value = fallbackWeekStart diff --git a/app/composables/useCurrentFormGroup.ts b/app/composables/useCurrentFormGroup.ts index ba11d2c..3761260 100644 --- a/app/composables/useCurrentFormGroup.ts +++ b/app/composables/useCurrentFormGroup.ts @@ -24,24 +24,28 @@ export const useCurrentFormGroup = () => { FormGroup.value.activeFormGroup = activeFg try { - const formsAPIResponse = await $fetch('/api/form/list', { + const formsAPIResponse = await useFetch('/api/form/list', { + method: 'GET', query: { published: 1, formGroup: activeFg.id } }) - + FormGroup.value.formComponents = {} - - FormGroup.value.forms = formsAPIResponse.map((form) => { - const {Components, FormGroup: _removedFormGroupField, startDate, endDate, ...restOfForm} = form + FormGroup.value.forms = [] + + if (formsAPIResponse.data.value) { + FormGroup.value.formComponents = {} + FormGroup.value.forms = formsAPIResponse.data.value.map((form) => { + const {Components, FormGroup: _removedFormGroupField, startDate, endDate, ...restOfForm} = form - FormGroup.value.formComponents[form.id] = Components ?? [] - - return { - ...restOfForm, - startDate: dayjs(startDate).toDate(), - endDate: endDate ? dayjs(endDate).toDate() : null, - } - }) + FormGroup.value.formComponents[form.id] = Components ?? [] + return { + ...restOfForm, + startDate: dayjs(startDate).toDate(), + endDate: endDate ? dayjs(endDate).toDate() : null, + } + }) + } } catch (error) { console.error('Failed to load forms for active form group:', error) FormGroup.value.forms = [] diff --git a/server/api/form/index.ts b/server/api/form/index.ts index bdbeb56..40ce340 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -5,7 +5,6 @@ import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' type ActionName = - | 'resolveFormGroupRangeByDate' | 'getFormGroupSubmissions' | 'createFormGroup' | 'createForm' @@ -192,27 +191,6 @@ export default defineEventHandler(async (event) => { if (method === 'GET') { const selectedAction = action ?? 'listFormGroups' - if (selectedAction === 'resolveFormGroupRangeByDate') { - const weeklyDate = toDate(getQuery(event).weeklyDate, 'weeklyDate') as Date - const matchingGroup = await findMatchingFormGroupByDate(weeklyDate) - - if (!matchingGroup) { - return { - found: false, - formGroupId: null, - startDate: null, - endDate: null, - } - } - - return { - found: true, - formGroupId: matchingGroup.id, - startDate: formatIsoDate(matchingGroup.startDate), - endDate: formatIsoDate(matchingGroup.endDate), - } - } - if (selectedAction === 'getFormGroupSubmissions') { const formGroupId = toInt(getQuery(event).formGroupId, 'formGroupId') diff --git a/server/api/form/list.get.ts b/server/api/form/list.get.ts index f3f35b4..774f94a 100644 --- a/server/api/form/list.get.ts +++ b/server/api/form/list.get.ts @@ -7,15 +7,16 @@ import { formInclude } from '../../utils/prismaInclusions' export default defineEventHandler(async (event) => { const query = getQuery(event) + //add requireSessions - const formGroupId = z.number().safeParse(query.formGroupId).data - const weeklyDate = z.date().safeParse(query.weeklyDate).data + const formGroupId = z.coerce.number().safeParse(query.formGroupId).data + const weeklyDate = z.coerce.date().safeParse(query.weeklyDate).data const where: Prisma.FormWhereInput = {} //expecting 0/1 for f/t if (query.published) { - const pre = z.number().safeParse(query.published) + const pre = z.coerce.number().safeParse(query.published) if (!pre.success) { throw createError({ statusCode: 400, message: 'Invalid published value' })} const published = z.coerce.boolean().safeParse(pre.data).data where.published = published diff --git a/server/api/formGroup/MatchByDate.get.ts b/server/api/formGroup/MatchByDate.get.ts index 7702d57..d1748bb 100644 --- a/server/api/formGroup/MatchByDate.get.ts +++ b/server/api/formGroup/MatchByDate.get.ts @@ -7,25 +7,32 @@ import z from 'zod' export default defineEventHandler(async (event) => { //session check needed const query = getQuery(event) - const date = z.date().safeParse(query.date).data + + if (!query.date) { + throw createError({ + statusCode: 400, + message: 'Missing Date', + }) + } + + const date = z.coerce.date().safeParse(query.date) - if (!date) { + if (!date.success) { throw createError({ statusCode: 400, - message: 'Invalid or Missing Date', + message: date.error.message }) } return await prisma.formGroup.findFirst({ where: { - startDate: { lte: date }, + startDate: { lte: date.data }, OR: [ { endDate: null }, - { endDate: { gte: date } }, + { endDate: { gte: date.data } }, ], }, orderBy: [{ startDate: 'desc' }, { id: 'desc' }], - select: { id: true, startDate: true, endDate: true }, + select: { id: true, startDate: true, endDate: true } }) - }) \ No newline at end of file diff --git a/server/api/formGroup/[id].get.ts b/server/api/formGroup/[id].get.ts index 269bf8a..626b4af 100644 --- a/server/api/formGroup/[id].get.ts +++ b/server/api/formGroup/[id].get.ts @@ -8,10 +8,25 @@ import { formGroupInclude } from '../../utils/prismaInclusions' export default defineEventHandler(async (event) => { //add auth - const groupId = z.number().safeParse(event.context.params?.id).data + const idParam = getRouterParam(event, 'id') + + if (!idParam) { + throw createError({ + statusCode: 400, + message: "Missing FormGroup id" + }) + } + const groupId = z.coerce.number().safeParse(idParam) + + if (!groupId.success) { + throw createError({ + statusCode: 400, + message: groupId.error.message + }) + } const group = await prisma.formGroup.findUnique({ - where: { id: groupId as number }, + where: { id: groupId.data as number }, include: formGroupInclude, }) From f3c4343de93eaf03b2a2607d82e8ad34c031fd6f Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 11 Jun 2026 13:49:28 -0500 Subject: [PATCH 13/18] formgroup and form post handlers --- server/api/announcement/index.ts | 4 +- server/api/form/index.post.ts | 57 ++++++++++++++++++++ server/api/form/index.ts | 86 ------------------------------ server/api/formGroup/index.post.ts | 45 ++++++++++++++++ server/utils/schemas.ts | 8 +-- 5 files changed, 108 insertions(+), 92 deletions(-) create mode 100644 server/api/form/index.post.ts create mode 100644 server/api/formGroup/index.post.ts diff --git a/server/api/announcement/index.ts b/server/api/announcement/index.ts index 4918225..b8712aa 100644 --- a/server/api/announcement/index.ts +++ b/server/api/announcement/index.ts @@ -36,8 +36,8 @@ export default defineEventHandler(async (event) => { return await prisma.announcement.create({ data: { content: body.data.content, - postDate: new Date(body.data.postDate), - expiryDate: body.data.expiryDate ? new Date(body.data.expiryDate) : null, + postDate: body.data.postDate, + expiryDate: body.data.expiryDate ?? body.data.expiryDate, author: body.data.author, } }) diff --git a/server/api/form/index.post.ts b/server/api/form/index.post.ts new file mode 100644 index 0000000..af4fd6c --- /dev/null +++ b/server/api/form/index.post.ts @@ -0,0 +1,57 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import { formInclude } from '../../utils/prismaInclusions' +import { formCreateSchema } from '../../utils/schemas' + +export default defineEventHandler (async (event) => { + + //require admin + const body = await readBody(event) + + if (!body) { + throw createError({ + statusCode: 400, + message: "Missing Request Body" + }) + } + + const form = formCreateSchema.safeParse(body) + + if (!form.success) { + throw createError({ + statusCode: 400, + message: form.error.message + }) + } + + const requestedGroupExists = await prisma.formGroup.findUnique({ + where: {id: form.data.formGroup}, }).then(Boolean) + + if (!requestedGroupExists) { + throw createError({ + statusCode: 400, + message: "FormGroup Does Not Exist" + }) + } + + const existingMax = await prisma.form.aggregate({ + where: { formGroup: form.data.formGroup }, + _max: { order: true }, + }) + + form.data.order = ((existingMax._max.order ?? -1) + 1) + + const created = await prisma.form.create({ + data: form.data as Prisma.FormUncheckedCreateInput, + include: formInclude + }) + + return { + success: true, + message: 'Form Created', + data: created + } + +}) \ No newline at end of file diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 40ce340..6af99a8 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -5,8 +5,6 @@ import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' type ActionName = - | 'getFormGroupSubmissions' - | 'createFormGroup' | 'createForm' | 'createComponent' | 'updateFormGroup' @@ -186,91 +184,7 @@ export default defineEventHandler(async (event) => { const body = method === 'GET' ? null : ((await readBody(event).catch(() => null)) as Record | null) const action = getAction(event, body) - if (method !== 'GET' && !action) { throw createError({ statusCode: 400, statusMessage: 'Missing action' }) } - - if (method === 'GET') { - const selectedAction = action ?? 'listFormGroups' - - if (selectedAction === 'getFormGroupSubmissions') { - const formGroupId = toInt(getQuery(event).formGroupId, 'formGroupId') - - const forms = await prisma.form.findMany({ - where: { formGroup: formGroupId as number }, - select: { id: true }, - }) - - const formIds = forms.map((f) => f.id) - - if (formIds.length === 0) { - return { submissions: [], totalEntries: 0 } - } - - const submissions = await prisma.formSubmission.findMany({ - where: { form: { in: formIds } }, - include: { - Student: true, - Form: { select: { id: true, title: true, startDate: true } }, - }, - }) - - return { - submissions: submissions.map((sub) => ({ - id: sub.id, - studentId: sub.student, - studentName: sub.Student.name, - formId: sub.form, - formTitle: sub.Form.title ?? `Form ${sub.Form.id}`, - submissionDate: sub.submissionDate.toISOString(), - })), - totalEntries: submissions.length, - } - } - - throw createError({ statusCode: 400, statusMessage: 'Unknown action' }) - } - - if (method === 'POST') { - if (action === 'createFormGroup') { - const startDate = toDate(body?.startDate, 'startDate') - const endDate = hasOwnField(body, 'endDate') ? toDate(body?.endDate, 'endDate', false) : null - const raffleWinner = hasOwnField(body, 'raffleWinner') ? toInt(body?.raffleWinner, 'raffleWinner', false) : null - - if (raffleWinner !== null) { - const student = await prisma.student.findUnique({ - where: { id: raffleWinner }, - select: { id: true }, - }) - - if (!student) { - throw createError({ statusCode: 404, statusMessage: 'Raffle winner student not found' }) - } - } - - const created = await prisma.formGroup.create({ - data: { - startDate: startDate as Date, - endDate, - raffleWinner, - }, - include: formGroupInclude, - }) - - setResponseStatus(event, 201) - - return { - success: true, - message: 'Form group created', - data: { - id: created.id, - startDate: formatIsoDate(created.startDate), - endDate: formatIsoDate(created.endDate), - raffleWinner: created.raffleWinner, - raffleWinnerStudent: created.RaffleWinner, - forms: created.Forms.map((form) => mapForm(form, created.startDate)), - }, - } - } if (action === 'createForm') { const startDate = toDate(body?.startDate, 'startDate') as Date diff --git a/server/api/formGroup/index.post.ts b/server/api/formGroup/index.post.ts new file mode 100644 index 0000000..662012b --- /dev/null +++ b/server/api/formGroup/index.post.ts @@ -0,0 +1,45 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import { formGroupInclude } from '../../utils/prismaInclusions' +import { formGroupCreateSchema } from '../../utils/schemas' + +export default defineEventHandler(async (event) => { + //require admin + const body = await readBody(event) + if (!body) { + throw createError({ + statusCode: 400, + message: "Missing Request Body" + }) + } + + const formGroup = formGroupCreateSchema.safeParse(body) + + if (!formGroup.success) { + throw createError({ + statusCode: 400, + message: formGroup.error.message + }) + } + + const created = await prisma.formGroup.create({ + data: formGroup.data as Prisma.FormGroupUncheckedCreateInput, + include: formGroupInclude, + }) + + return { + success: true, + message: 'Form group create', + data: { + id: created.id, + startDate: created.startDate, + endDate: created.endDate, + raffleWinner: null, + raffleWinnerStudent: null, + forms: created.Forms + } + } + +}) \ No newline at end of file diff --git a/server/utils/schemas.ts b/server/utils/schemas.ts index 6520b80..4c5c3ea 100644 --- a/server/utils/schemas.ts +++ b/server/utils/schemas.ts @@ -42,15 +42,15 @@ export const announcementCreateSchema = z.object({ export const formGroupCreateSchema = z.object({ startDate: z.coerce.date(), - endDate: z.coerce.date(), + endDate: z.coerce.date().nullish(), }) export const formCreateSchema = z.object({ - order: z.int(), + order: z.int().nullish(), startDate: z.coerce.date(), - endDate: z.coerce.date(), + endDate: z.coerce.date().nullish(), published: z.boolean(), - author: z.cuid2(), + author: z.cuid2().nullish(), formGroup: z.int(), title: z.string().min(1).max(250), }) From b782fc5e5d28a18f6a3f550adec2a99566149762 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 11 Jun 2026 14:21:35 -0500 Subject: [PATCH 14/18] beginning of endpoint swap for form post --- app/composables/useAdmin.ts | 16 +++++----- server/api/form/index.ts | 58 ------------------------------------- 2 files changed, 9 insertions(+), 65 deletions(-) diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index 4955604..e5981ce 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -2,6 +2,7 @@ // Place this at: app/composables/useAdmin.ts import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' +import type { Form } from '~~/prisma/generated/client' dayjs.extend(utc) export const useAdmin = () => { @@ -392,15 +393,16 @@ export const useAdmin = () => { } startDate.setDate(startDate.getDate() + dayIndex) - - const createdFormResponse = await callFormApi('POST', {}, { - action: 'createForm', - startDate: formatYmdLocal(startDate), - published: true, - title: formTitle.value, + const createdFormResponse: any = await useFetch('api/form', { + method: 'POST', + body: { + startDate: dayjs(startDate).toISOString(), + published: true, + title: formTitle.value, + } }) - const createdForm = createdFormResponse?.data + const createdForm: Form = createdFormResponse?.data.value if (!createdForm?.id) { continue diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 6af99a8..db8c4b9 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -5,7 +5,6 @@ import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' type ActionName = - | 'createForm' | 'createComponent' | 'updateFormGroup' | 'updateComponent' @@ -186,63 +185,6 @@ export default defineEventHandler(async (event) => { if (method === 'POST') { - if (action === 'createForm') { - const startDate = toDate(body?.startDate, 'startDate') as Date - const endDate = hasOwnField(body, 'endDate') ? toDate(body?.endDate, 'endDate', false) : null - const published = hasOwnField(body, 'published') ? toBoolean(body?.published) : false - const title = hasOwnField(body, 'title') ? String(body?.title ?? '').trim() || null : null - const explicitOrder = hasOwnField(body, 'order') ? toInt(body?.order, 'order', false) : null - const requestedGroupId = hasOwnField(body, 'formGroup') - ? toInt(body?.formGroup, 'formGroup', false) - : null - - let resolvedFormGroupId: number - - if (requestedGroupId !== null) { - const requestedGroup = await prisma.formGroup.findUnique({ - where: { id: requestedGroupId }, - select: { id: true }, - }) - - resolvedFormGroupId = requestedGroup - ? requestedGroup.id - : await findOrCreateWeeklyFormGroup(startDate) - } else { - resolvedFormGroupId = await findOrCreateWeeklyFormGroup(startDate) - } - - const existingMax = await prisma.form.aggregate({ - where: { formGroup: resolvedFormGroupId }, - _max: { order: true }, - }) - - const createData: Record = { - formGroup: resolvedFormGroupId, - startDate, - endDate, - published, - order: explicitOrder ?? ((existingMax._max.order ?? -1) + 1), - author: admin?.id ?? null, - } - - if (title) { - createData.title = title - } - - const created = await prisma.form.create({ - data: createData as Prisma.FormUncheckedCreateInput, - include: formInclude, - }) - - setResponseStatus(event, 201) - - return { - success: true, - message: 'Form created', - data: mapForm(created, created.FormGroup.startDate), - } - } - if (action === 'createComponent') { const form = toInt(body?.form, 'form') const questionType = String(body?.questionType ?? '').trim() From 14079451962a737d26ab97eadbde98d80711f43c Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 11 Jun 2026 14:44:52 -0500 Subject: [PATCH 15/18] form component post --- server/api/form/index.post.ts | 4 +- server/api/formComponent/index.post.ts | 59 ++++++++++++++++++++++++++ server/utils/schemas.ts | 2 +- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 server/api/formComponent/index.post.ts diff --git a/server/api/form/index.post.ts b/server/api/form/index.post.ts index af4fd6c..ec2d656 100644 --- a/server/api/form/index.post.ts +++ b/server/api/form/index.post.ts @@ -31,8 +31,8 @@ export default defineEventHandler (async (event) => { if (!requestedGroupExists) { throw createError({ - statusCode: 400, - message: "FormGroup Does Not Exist" + statusCode: 404, + message: "FormGroup Not Found" }) } diff --git a/server/api/formComponent/index.post.ts b/server/api/formComponent/index.post.ts new file mode 100644 index 0000000..60b06b4 --- /dev/null +++ b/server/api/formComponent/index.post.ts @@ -0,0 +1,59 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import { formInclude } from '../../utils/prismaInclusions' +import { formComponentCreateSchema } from '../../utils/schemas' + + +export default defineEventHandler(async (event) => { + //require admin + + const body = await readBody(event) + + if (!body) { + throw createError({ + statusCode: 400, + message: "Missing Request Body" + }) + } + + const component = formComponentCreateSchema.safeParse(body) + + if (!component.success) { + throw createError({ + statusCode:400, + message: component.error.message + }) + } + + const formExists = await prisma.form.findUnique({ + where: { id: component.data.form} + }).then(Boolean) + + if (!formExists) { + throw createError({ + statusCode: 404, + message: "Form Not Found" + }) + } + + const existingMax = await prisma.formComponent.aggregate({ + where: { form: component.data.form }, + _max: { order: true }, + }) + + component.data.order = component.data.order ?? ((existingMax._max.order ?? -1) + 1) + + const created = await prisma.formComponent.create({ + data: component.data as Prisma.FormComponentUncheckedCreateInput + }) + + setResponseStatus(event, 201) + + return { + success: true, + message: 'Form component created', + data: created + } +}) \ No newline at end of file diff --git a/server/utils/schemas.ts b/server/utils/schemas.ts index 4c5c3ea..5bcb4a1 100644 --- a/server/utils/schemas.ts +++ b/server/utils/schemas.ts @@ -73,7 +73,7 @@ export const formComponentCreateSchema = z.object({ questionOptions: z.object({ choices: z.array(z.object({text: z.string().min(1), correct: z.boolean()})).optional(), video: z.string().optional() - }) + }).nullish() }) export const formSubmissionCreateSchema = z.object({ From bb322aaadad69648619a4d71f51291ecd2aaee24 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 11 Jun 2026 15:11:16 -0500 Subject: [PATCH 16/18] formgroup and formcomponent put handlers --- server/api/form/[id].put.ts | 12 +- server/api/form/[id].ts | 0 server/api/form/index.ts | 175 +-------------------------- server/api/formComponent/[id].put.ts | 47 +++++++ server/api/formGroup/[id].put.ts | 39 ++++++ server/utils/schemas.ts | 12 ++ 6 files changed, 109 insertions(+), 176 deletions(-) delete mode 100644 server/api/form/[id].ts create mode 100644 server/api/formComponent/[id].put.ts create mode 100644 server/api/formGroup/[id].put.ts diff --git a/server/api/form/[id].put.ts b/server/api/form/[id].put.ts index dbc40fa..bf6ed17 100644 --- a/server/api/form/[id].put.ts +++ b/server/api/form/[id].put.ts @@ -8,13 +8,21 @@ import { z } from 'zod' export default eventHandler(async (event) => { //require sessions + const idParam = getRouterParam(event, 'id') + if (!idParam) { + throw createError({ + statusCode: 400, + message: "Missing ID" + }) + } + const body = formUpdateSchema.safeParse(await readBody(event)) if (!body.success) {throw createError({ statusCode: 400, message: body.error.message })} - const id = z.number().safeParse(event.context.params?.id) + const id = z.coerce.number().safeParse(idParam) - if (!id.success || !id.data) {throw createError({ statusCode: 400, message: 'Missing or Invalid form ID'})} + if (!id.success) {throw createError({ statusCode: 400, message: 'Invalid form ID'})} return await prisma.form.update({ where: { id: id.data }, diff --git a/server/api/form/[id].ts b/server/api/form/[id].ts deleted file mode 100644 index e69de29..0000000 diff --git a/server/api/form/index.ts b/server/api/form/index.ts index db8c4b9..31a970a 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -5,10 +5,7 @@ import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' type ActionName = - | 'createComponent' - | 'updateFormGroup' - | 'updateComponent' - | 'deleteFormGroup' +'deleteFormGroup' | 'deleteComponent' const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] @@ -183,176 +180,6 @@ export default defineEventHandler(async (event) => { const body = method === 'GET' ? null : ((await readBody(event).catch(() => null)) as Record | null) const action = getAction(event, body) - if (method === 'POST') { - - if (action === 'createComponent') { - const form = toInt(body?.form, 'form') - const questionType = String(body?.questionType ?? '').trim() - const questionText = String(body?.questionText ?? '').trim() - - if (!questionType) { - throw createError({ statusCode: 400, statusMessage: 'questionType is required' }) - } - - if (!questionText) { - throw createError({ statusCode: 400, statusMessage: 'questionText is required' }) - } - - const parentForm = await prisma.form.findUnique({ - where: { id: form as number }, - select: { id: true }, - }) - - if (!parentForm) { - throw createError({ statusCode: 404, statusMessage: 'Form not found' }) - } - - const explicitOrder = hasOwnField(body, 'order') ? toInt(body?.order, 'order', false) : null - - const existingMax = await prisma.formComponent.aggregate({ - where: { form: form as number }, - _max: { order: true }, - }) - - const created = await prisma.formComponent.create({ - data: { - form: form as number, - order: explicitOrder ?? ((existingMax._max.order ?? -1) + 1), - questionType, - questionText, - questionOptions: toOptionalJson(body?.questionOptions) ?? null, - }, - }) - - setResponseStatus(event, 201) - - return { - success: true, - message: 'Form component created', - data: mapComponent(created), - } - } - - throw createError({ statusCode: 400, statusMessage: 'Unknown action' }) - } - - if (method === 'PUT') { - if (action === 'updateFormGroup') { - const id = toInt(body?.id, 'id') - const data: { - startDate?: Date - endDate?: Date | null - raffleWinner?: number | null - } = {} - - if (hasOwnField(body, 'startDate')) { - data.startDate = toDate(body?.startDate, 'startDate') as Date - } - - if (hasOwnField(body, 'endDate')) { - data.endDate = toDate(body?.endDate, 'endDate', false) - } - - if (hasOwnField(body, 'raffleWinner')) { - const raffleWinner = toInt(body?.raffleWinner, 'raffleWinner', false) - - if (raffleWinner !== null) { - const student = await prisma.student.findUnique({ - where: { id: raffleWinner }, - select: { id: true }, - }) - - if (!student) { - throw createError({ statusCode: 404, statusMessage: 'Raffle winner student not found' }) - } - } - - data.raffleWinner = raffleWinner - } - - const updated = await prisma.formGroup.update({ - where: { id: id as number }, - data, - include: formGroupInclude, - }) - - return { - success: true, - message: 'Form group updated', - data: { - id: updated.id, - startDate: formatIsoDate(updated.startDate), - endDate: formatIsoDate(updated.endDate), - raffleWinner: updated.raffleWinner, - raffleWinnerStudent: updated.RaffleWinner, - forms: updated.Forms.map((form) => mapForm(form, updated.startDate)), - }, - } - } - - if (action === 'updateComponent') { - const id = toInt(body?.id, 'id') - const data: Prisma.FormComponentUpdateInput = {} - - if (hasOwnField(body, 'form')) { - const form = toInt(body?.form, 'form') - const parentForm = await prisma.form.findUnique({ - where: { id: form as number }, - select: { id: true }, - }) - - if (!parentForm) { - throw createError({ statusCode: 404, statusMessage: 'Form not found' }) - } - - data.Form = { - connect: { id: form as number }, -} - } - - if (hasOwnField(body, 'order')) { - data.order = toInt(body?.order, 'order') as number - } - - if (hasOwnField(body, 'questionType')) { - const questionType = String(body?.questionType ?? '').trim() - - if (!questionType) { - throw createError({ statusCode: 400, statusMessage: 'questionType is required' }) - } - - data.questionType = questionType - } - - if (hasOwnField(body, 'questionText')) { - const questionText = String(body?.questionText ?? '').trim() - - if (!questionText) { - throw createError({ statusCode: 400, statusMessage: 'questionText is required' }) - } - - data.questionText = questionText - } - - if (hasOwnField(body, 'questionOptions')) { - data.questionOptions = toOptionalJson(body?.questionOptions) - } - - const updated = await prisma.formComponent.update({ - where: { id: id as number }, - data, - }) - - return { - success: true, - message: 'Form component updated', - data: mapComponent(updated), - } - } - - throw createError({ statusCode: 400, statusMessage: 'Unknown action' }) - } - if (method === 'DELETE') { if (action === 'deleteFormGroup') { const id = toInt(body?.id ?? getQuery(event).id, 'id') diff --git a/server/api/formComponent/[id].put.ts b/server/api/formComponent/[id].put.ts new file mode 100644 index 0000000..88fa4b2 --- /dev/null +++ b/server/api/formComponent/[id].put.ts @@ -0,0 +1,47 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { z } from 'zod' +import { requireAdmin, requireSession } from '../../utils/require-session' +import { formComponentUpdateSchema } from '../../utils/schemas' + +export default defineEventHandler(async (event) => { + //require admin + const idParam = getRouterParam(event, 'id') + if (!idParam) { + throw createError({ + statusCode: 400, + message: "Missing ID" + }) + } + + const body = await readBody(event) + const component = formComponentUpdateSchema.safeParse(body) + + if (!component.success) {throw createError({ statusCode: 400, message: component.error.message })} + + const id = z.coerce.number().safeParse(idParam) + + if (!id.success) {throw createError({ statusCode: 400, message: 'Invalid ID Parameter'})} + + const formExists = await prisma.form.findUnique({ + where: {id: component.data.form} + }).then(Boolean) + + if (!formExists) { + throw createError({ + statusCode: 404, + message: "Form Not Found" + }) + } + const updated = await prisma.formComponent.update({ + where: {id: id.data}, + data: component.data as Prisma.FormComponentUncheckedUpdateInput + }) + + return { + success: true, + message: 'Form Component Updated', + data: updated + } +}) \ No newline at end of file diff --git a/server/api/formGroup/[id].put.ts b/server/api/formGroup/[id].put.ts new file mode 100644 index 0000000..236d27d --- /dev/null +++ b/server/api/formGroup/[id].put.ts @@ -0,0 +1,39 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { z } from 'zod' +import { requireAdmin, requireSession } from '../../utils/require-session' +import { formGroupInclude } from '../../utils/prismaInclusions' +import { formGroupUpdateSchema } from '../../utils/schemas' + +export default defineEventHandler(async (event) => { + //require admin + const idParam = getRouterParam(event, 'id') + if (!idParam) { + throw createError({ + statusCode: 400, + message: "Missing ID" + }) + } + + const body = await readBody(event) + const group = formGroupUpdateSchema.safeParse(body) + + if (!group.success) {throw createError({ statusCode: 400, message: group.error.message })} + + const id = z.coerce.number().safeParse(idParam) + + if (!id.success) {throw createError({ statusCode: 400, message: 'Invalid ID Parameter'})} + + const updated = await prisma.formGroup.update({ + where: {id: id.data}, + data: group.data as Prisma.FormGroupUncheckedUpdateInput, + include: formGroupInclude + }) + + return { + success: true, + message: 'FormGroup Updated', + data: updated + } +}) \ No newline at end of file diff --git a/server/utils/schemas.ts b/server/utils/schemas.ts index 5bcb4a1..ee13f9a 100644 --- a/server/utils/schemas.ts +++ b/server/utils/schemas.ts @@ -45,6 +45,12 @@ export const formGroupCreateSchema = z.object({ endDate: z.coerce.date().nullish(), }) +export const formGroupUpdateSchema = z.object({ + startDate: z.coerce.date().optional(), + endDate: z.coerce.date().nullish(), + raffleWinner: z.int().optional() +}) + export const formCreateSchema = z.object({ order: z.int().nullish(), startDate: z.coerce.date(), @@ -76,6 +82,10 @@ export const formComponentCreateSchema = z.object({ }).nullish() }) +export const formComponentUpdateSchema = z.object({ + +}) + export const formSubmissionCreateSchema = z.object({ student: z.int(), form: z.int(), @@ -97,6 +107,8 @@ export type AnnouncementCreate = z.infer export type FormCreate = z.infer export type FormUpdate = z.infer export type FormGroupCreate = z.infer +export type FormGroupUpdate = z.infer export type FormComponentCreate = z.infer +export type FormComponentUpdate = z.infer export type FormSubmissionCreate = z.infer export type SubmissionResponseCreate = z.infer \ No newline at end of file From c8d0cb2f42c1f50a1d721360fa7da2dcc25eca7c Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 11 Jun 2026 16:49:41 -0500 Subject: [PATCH 17/18] formgroup and component delete handlers --- server/api/form/index.ts | 16 +--------- server/api/formComponent/[id].delete.ts | 37 +++++++++++++++++++++++ server/api/formGroup/[id].delete.ts | 39 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 server/api/formComponent/[id].delete.ts create mode 100644 server/api/formGroup/[id].delete.ts diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 31a970a..5766ffa 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -5,8 +5,7 @@ import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' type ActionName = -'deleteFormGroup' - | 'deleteComponent' +'deleteComponent' const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] @@ -181,19 +180,6 @@ export default defineEventHandler(async (event) => { const action = getAction(event, body) if (method === 'DELETE') { - if (action === 'deleteFormGroup') { - const id = toInt(body?.id ?? getQuery(event).id, 'id') - - await prisma.formGroup.delete({ - where: { id: id as number }, - }) - - return { - success: true, - message: 'Form group deleted', - } - } - if (action === 'deleteComponent') { const id = toInt(body?.id ?? getQuery(event).id, 'id') diff --git a/server/api/formComponent/[id].delete.ts b/server/api/formComponent/[id].delete.ts new file mode 100644 index 0000000..70f4465 --- /dev/null +++ b/server/api/formComponent/[id].delete.ts @@ -0,0 +1,37 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' + + +export default defineEventHandler(async (event) => { + //require admin + + const idParam = getRouterParam(event, 'id') + + if (!idParam) { + throw createError({ + statusCode: 400, + message: "Missing ID Parameter" + }) + } + + const id = z.coerce.number().safeParse(idParam) + + if (!id.success) { + throw createError({ + statusCode:400, + message: id.error.message + }) + } + + await prisma.formComponent.delete({ + where: { id: id.data } + }) + + return { + success: true, + message: 'Form Component Deleted' + } +}) \ No newline at end of file diff --git a/server/api/formGroup/[id].delete.ts b/server/api/formGroup/[id].delete.ts new file mode 100644 index 0000000..b82c0a6 --- /dev/null +++ b/server/api/formGroup/[id].delete.ts @@ -0,0 +1,39 @@ +import { prisma } from '../../utils/prisma' +import { Prisma } from '~~/prisma/generated/client' +import { getQuery, createError } from 'h3' +import { requireAdmin, requireSession } from '../../utils/require-session' +import z from 'zod' +import dayjs from 'dayjs' +import { formGroupInclude } from '../../utils/prismaInclusions' + +export default defineEventHandler(async (event) => { + //require admin + + const idParam = getRouterParam(event, 'id') + + if (!idParam) { + throw createError({ + statusCode: 400, + message: "Missing FormGroup id" + }) + } + + const group = z.coerce.number().safeParse(idParam) + + if (!group.success) { + throw createError({ + statusCode: 400, + message: group.error.message + }) + } + + await prisma.formGroup.delete({ + where: {id: group.data} + }) + + return { + success: true, + message: 'Form Group deleted', + } + +}) \ No newline at end of file From 953cb01dc81e78d7896444c0127597afc8c0bd89 Mon Sep 17 00:00:00 2001 From: AidanLoran Date: Thu, 11 Jun 2026 16:54:09 -0500 Subject: [PATCH 18/18] handlers fixed, need to update endpoints, migrate helpers, and merge with dayjs logic update --- server/api/form/index.ts | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/server/api/form/index.ts b/server/api/form/index.ts index 5766ffa..6b55fe8 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -4,9 +4,6 @@ import { prisma } from '~~/server/utils/prisma' import { getQuery, setResponseStatus, type H3Event } from 'h3' import {z } from 'zod' -type ActionName = -'deleteComponent' - const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] const hasOwnField = (value: Record | null, key: string) => @@ -174,27 +171,4 @@ const mapForm = ( } } -export default defineEventHandler(async (event) => { - const method = event.node.req.method ?? 'GET' - const body = method === 'GET' ? null : ((await readBody(event).catch(() => null)) as Record | null) - const action = getAction(event, body) - - if (method === 'DELETE') { - if (action === 'deleteComponent') { - const id = toInt(body?.id ?? getQuery(event).id, 'id') - - await prisma.formComponent.delete({ - where: { id: id as number }, - }) - - return { - success: true, - message: 'Form component deleted', - } - } - - throw createError({ statusCode: 400, statusMessage: 'Unknown action' }) - } - - throw createError({ statusCode: 405, statusMessage: 'Method not allowed' }) -}) +export default defineEventHandler(async (event) => {})