integratedhub/routes/api/update/tenant_invite.ts
2026-01-20 09:02:26 -06:00

182 lines
5.9 KiB
TypeScript

// 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<string>();
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<string, unknown> = {};
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<TenantInvite> = {};
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" } },
);
}
},
});