import type { PageServerLoad, Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; import { db } from '$lib/server/db'; import { applications } from '$lib/server/schema'; import { sendApplicationNotification } from '$lib/server/email'; import { loginLimiter } from '$lib/server/rate-limit'; export const load: PageServerLoad = async ({ parent }) => { const parentData = await parent(); if (!parentData.orgConfig.applications.fosterEnabled) { throw redirect(302, '/'); } return parentData; }; export const actions: Actions = { default: async ({ request, getClientAddress, locals }) => { const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? getClientAddress(); if (!loginLimiter.check(`apply-foster:${ip}`)) { return fail(429, { error: 'Too many submissions. Please try again later.' }); } const formData = await request.formData(); const firstName = formData.get('firstName')?.toString().trim() ?? ''; const lastName = formData.get('lastName')?.toString().trim() ?? ''; const applicantName = `${firstName} ${lastName}`.trim(); const email = formData.get('email')?.toString().trim() ?? ''; const phone = formData.get('phone')?.toString().trim() ?? ''; const data: Record = {}; const fieldNames = [ 'firstName', 'lastName', 'address1', 'address2', 'city', 'state', 'zip', 'ownRent', 'landlordInfo', 'householdMembers', 'currentAnimals', 'otherPropertyPets', 'previousPets', 'petsVaccinated', 'yardFenced', 'fenceHeight', 'whereDuringDay', 'whereSleepNight', 'hoursAlone', 'activityLevel', 'whyFoster', 'howHeard', 'agreeLeash', 'integratedBefore', 'twoWeekShutdown', 'felony', 'felonyDetails', 'animalNeglect', 'reference1Name', 'reference1Phone', 'reference1Relationship', 'reference2Name', 'reference2Phone', 'reference2Relationship', 'reference3Name', 'reference3Phone', 'reference3Relationship', 'vetName', 'vetClinic', 'vetPhone', 'landlordName', 'landlordPhone', 'acknowledge', 'signature' ]; for (const name of fieldNames) { data[name] = formData.get(name)?.toString() ?? ''; } data.petType = formData.getAll('petType').map((v) => v.toString()); data.traits = formData.getAll('traits').map((v) => v.toString()); for (let i = 1; i <= 20; i++) { data[`agreement${i}`] = formData.get(`agreement${i}`)?.toString() ?? ''; } const errors: string[] = []; if (!firstName) errors.push('First name is required.'); if (!lastName) errors.push('Last name is required.'); if (!email) errors.push('Email is required.'); if (!phone) errors.push('Phone number is required.'); if (!data.address1) errors.push('Address is required.'); if (!data.city) errors.push('City is required.'); if (!data.state) errors.push('State is required.'); if (!data.zip) errors.push('ZIP code is required.'); if (!data.ownRent) errors.push('Own/rent is required.'); if (!data.householdMembers) errors.push('Household members is required.'); if (!data.currentAnimals) errors.push('Current animals is required.'); if (!data.previousPets) errors.push('Previous pets is required.'); if (!data.petsVaccinated) errors.push('Vaccination status is required.'); if (!data.yardFenced) errors.push('Yard fencing status is required.'); if (!data.whereDuringDay) errors.push('Where animal stays during the day is required.'); if (!data.whereSleepNight) errors.push('Where animal sleeps at night is required.'); if (!data.hoursAlone) errors.push('Hours alone is required.'); if (!data.activityLevel) errors.push('Activity level is required.'); if (!data.whyFoster) errors.push('Why you want to foster is required.'); if (!data.howHeard) errors.push('How you heard about us is required.'); if (!data.agreeLeash) errors.push('Leash agreement is required.'); if (!data.integratedBefore) errors.push('Integration experience is required.'); if (!data.twoWeekShutdown) errors.push('2-week shutdown response is required.'); if (!data.felony) errors.push('Felony question is required.'); if (!data.animalNeglect) errors.push('Animal neglect question is required.'); if (!data.reference1Name || !data.reference1Phone) errors.push('Reference 1 is required.'); if (!data.reference2Name || !data.reference2Phone) errors.push('Reference 2 is required.'); if (!data.reference3Name || !data.reference3Phone) errors.push('Reference 3 is required.'); if (!data.vetName || !data.vetClinic || !data.vetPhone) errors.push('Veterinarian information is required.'); if (!data.landlordName || !data.landlordPhone) errors.push('Landlord information is required (put N/A if you own).'); if (data.acknowledge !== 'yes') errors.push('You must acknowledge the foster agreement.'); if (!data.signature) errors.push('Signature is required.'); for (let i = 1; i <= 20; i++) { if (!data[`agreement${i}`]) { errors.push(`Agreement item ${i} is required.`); break; } } if (errors.length > 0) { return fail(400, { error: errors.join(' '), firstName, lastName, applicantName, email, phone, ...data }); } await db.insert(applications).values({ type: 'foster', petId: null, applicantName, applicantEmail: email, applicantPhone: phone || null, formData: data }); const orgConfig = locals.orgConfig; if (orgConfig.orgInfo.email) { try { sendApplicationNotification( orgConfig.orgInfo.name, orgConfig.orgInfo.email, applicantName, 'foster' ); } catch { // Email failure should not break form submission } } return { success: true }; } };