// routes/api/update/tenant_invite.ts import { define } from "../../../utils.ts"; import { getPropertyById } from "../../../services/propertyService.ts"; import { createTenantInvite, getTenantInviteById, getTenantInviteByProperty, updateTenantInvite, } from "../../../services/tenantInviteService.ts"; import { SessionCookie } from "../../auth/callback.ts"; import type { TenantInvite } from "../../../db/interfaces.ts"; function parseInvitedEmails(input: unknown) { if (Array.isArray(input)) { return input.map((item) => String(item).trim()).filter(Boolean); } if (typeof input === "string") { return input.split(/[\n,]+/).map((item) => item.trim()).filter(Boolean); } return []; } function parseStoredEmails(input: string | null | undefined) { if (!input) return []; try { const parsed = JSON.parse(input); if (Array.isArray(parsed)) { return parsed.map((item) => String(item).trim()).filter(Boolean); } } catch (_error) { // Fall back to delimited parsing. } return parseInvitedEmails(input); } function mergeEmails(existing: string[], incoming: string[]) { const seen = new Set(); const merged: string[] = []; for (const email of [...existing, ...incoming]) { const key = email.toLowerCase(); if (seen.has(key)) continue; seen.add(key); merged.push(email); } return merged; } function parseAvailableOn(input: unknown) { if (typeof input === "number") return input; if (typeof input === "string" && input.trim() !== "") { const parsed = Number(input); if (!Number.isNaN(parsed)) return parsed; const parsedDate = Date.parse(input); return Number.isNaN(parsedDate) ? null : parsedDate; } return null; } export const handler = define.handlers({ async POST(ctx) { try { const stateData: SessionCookie = ctx.state.data; const { user_id } = stateData; if (!user_id) { return new Response( JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } }, ); } const contentType = ctx.req.headers.get("content-type") || ""; let data: Record = {}; if (contentType.includes("application/json")) { data = await ctx.req.json(); } else { const formData = await ctx.req.formData(); data = { invite_id: formData.get("invite_id")?.toString() || "", property_id: formData.get("property_id")?.toString() || "", available_on: formData.get("available_on")?.toString() || "", invited_emails: formData.get("invited_emails")?.toString() || "", }; } const inviteId = (data.invite_id || data.id || "").toString(); const propertyId = (data.property_id || "").toString(); const availableOn = parseAvailableOn(data.available_on); const invitedEmails = parseInvitedEmails(data.invited_emails); const hasAvailableOn = Object.prototype.hasOwnProperty.call( data, "available_on", ); const hasInvitedEmails = Object.prototype.hasOwnProperty.call( data, "invited_emails", ); if (!inviteId && !propertyId) { return new Response( JSON.stringify({ error: "Invite ID or property ID is required" }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } let invite = inviteId ? await getTenantInviteById(inviteId) : await getTenantInviteByProperty(user_id, propertyId); if (inviteId && (!invite || invite.landlord_id !== user_id)) { return new Response( JSON.stringify({ error: "Invite not found" }), { status: 404, headers: { "Content-Type": "application/json" } }, ); } if (propertyId) { const property = await getPropertyById(propertyId); if (!property || property.owner_id !== user_id) { return new Response( JSON.stringify({ error: "Invalid property selection" }), { status: 403, headers: { "Content-Type": "application/json" } }, ); } } if ( invite && propertyId && invite.property_id && invite.property_id !== propertyId ) { const propertyInvite = await getTenantInviteByProperty( user_id, propertyId, ); if (propertyInvite) { invite = propertyInvite; } } if (!invite && propertyId) { const created = await createTenantInvite(user_id, propertyId, { availableOn, }); invite = created[0] || null; } else if (!invite && !propertyId) { return new Response( JSON.stringify({ error: "Invite not found" }), { status: 404, headers: { "Content-Type": "application/json" } }, ); } let result = invite ? [invite] : []; if (invite && (propertyId || hasAvailableOn || hasInvitedEmails)) { const updates: Partial = {}; if (propertyId) updates.property_id = propertyId; if (hasAvailableOn) updates.available_on = availableOn; if (hasInvitedEmails) { const existingEmails = parseStoredEmails(invite.invited_emails); const mergedEmails = mergeEmails(existingEmails, invitedEmails); updates.invited_emails = JSON.stringify(mergedEmails); } result = Object.keys(updates).length > 0 ? await updateTenantInvite(invite.id, updates) : [invite]; } return new Response( JSON.stringify({ success: true, invite: result[0] || null }), { status: 200, headers: { "Content-Type": "application/json" } }, ); } catch (error) { return new Response( JSON.stringify({ error: error instanceof Error ? error.message : error, }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } }, });