Initial commit: Kinship Directory application

This commit is contained in:
2026-09-05 10:33:45 -05:00
commit 0eb4240f0a
51 changed files with 4682 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import type { Person } from "./types";
export const clean = (value: unknown, max = 120) => String(value || "").trim().slice(0, max);
export const normalizeEmail = (value: unknown) => clean(value, 254).toLowerCase();
export function isSameOrigin(request: Request): boolean {
const origin = request.headers.get("origin");
if (!origin) return true;
try {
const originUrl = new URL(origin);
const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
const protocol = request.headers.get("x-forwarded-proto");
return originUrl.host === host && (!protocol || originUrl.protocol === `${protocol}:`);
} catch {
return false;
}
}
export function isSecureRequest(request: Request): boolean {
const protocol = request.headers.get("x-forwarded-proto");
return protocol ? protocol === "https" : new URL(request.url).protocol === "https:";
}
export function isUnder18(birthday: string): boolean {
const slashDate = birthday.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
const date = slashDate
? new Date(Number(slashDate[3]), Number(slashDate[1]) - 1, Number(slashDate[2]))
: new Date(`${birthday}T00:00:00`);
if (Number.isNaN(date.getTime())) return false;
const today = new Date();
let age = today.getFullYear() - date.getFullYear();
const beforeBirthday = today.getMonth() < date.getMonth() ||
(today.getMonth() === date.getMonth() && today.getDate() < date.getDate());
if (beforeBirthday) age--;
return age >= 0 && age < 18;
}
function isValidBirthday(birthday: string): boolean {
if (!birthday) return true;
const monthDay = birthday.match(/^(\d{1,2})\/(\d{1,2})(?:\/(\d{4}))?$/);
if (monthDay) {
const month = Number(monthDay[1]); const day = Number(monthDay[2]); const year = Number(monthDay[3] || 2000);
const date = new Date(year, month - 1, day);
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
}
return /^\d{4}-\d{2}-\d{2}$/.test(birthday) && !Number.isNaN(new Date(`${birthday}T00:00:00`).getTime());
}
export function parsePeople(value: unknown, children = false): Person[] {
if (!Array.isArray(value)) return [];
return value.slice(0, 20).map((raw, index) => {
const person = raw as Record<string, unknown>;
const birthday = clean(person.birthday, 10);
if (!isValidBirthday(birthday)) throw new Error(`Person ${index + 1} has an invalid birthday.`);
const hasBirthYear = /^\d{4}-/.test(birthday) || /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(birthday);
if (children && hasBirthYear && !isUnder18(birthday)) throw new Error(`Child ${index + 1} must be under 18.`);
return {
id: clean(person.id, 50) || crypto.randomUUID(),
firstName: clean(person.firstName, 60),
lastName: clean(person.lastName, 60),
email: normalizeEmail(person.email) || undefined,
phone: clean(person.phone, 30) || undefined,
birthday: birthday || undefined,
photoId: clean(person.photoId, 50) || undefined,
};
}).filter((person) => person.firstName && person.lastName);
}