// services/tenantInviteService.ts // Handle tenant invite creation and updates import { and, eq, ilike, desc } from "drizzle-orm"; import { db } from "../db/db.ts"; import { tenant_invites } from "../db/schema.ts"; import type { TenantInvite } from "../db/interfaces.ts"; import { addE2ETenantInvite, deleteE2ETenantInvite, getE2ETwitterInviteById, getE2ETwitterInviteByProperty, getE2ETwitterInvitesByEmail, getE2ETenantInvitesByLandlord, isE2EEnabled, updateE2ETwitterInvite, } from "./e2eFixtures.ts"; function parseInviteEmails(rawEmails?: string | null) { if (!rawEmails) return []; try { const parsed = JSON.parse(rawEmails); if (!Array.isArray(parsed)) return []; return parsed.filter((entry) => typeof entry === "string"); } catch { return rawEmails.split(/[\n,]+/) .map((entry) => entry.trim()) .filter(Boolean); } } function normalizeInviteEmail(email: string) { return email.trim().toLowerCase(); } function hasMatchingInviteEmail( invite: TenantInvite, normalizedEmail: string, ) { const emails = parseInviteEmails(invite.invited_emails); return emails.some((email) => email.toLowerCase() === normalizedEmail); } export async function createTenantInvite( landlordId: string, propertyId: string, options: { availableOn?: number | null } = {}, ) { if (isE2EEnabled()) { return [addE2ETenantInvite(landlordId, propertyId, options)]; } const existing = await db.select().from(tenant_invites).where( and( eq(tenant_invites.landlord_id, landlordId), eq(tenant_invites.property_id, propertyId), ), ); if (existing.length > 0) { return existing; } const now = Date.now(); return await db.insert(tenant_invites).values({ id: crypto.randomUUID(), landlord_id: landlordId, property_id: propertyId, available_on: options.availableOn ?? null, invited_emails: JSON.stringify([]), created_on: now, updated_on: now, }).returning(); } export async function getTenantInviteById( inviteId: string, ): Promise { if (isE2EEnabled()) { return getE2ETwitterInviteById(inviteId); } const result = await db.select().from(tenant_invites).where( eq(tenant_invites.id, inviteId), ); return result[0] || null; } export async function getTenantInviteByProperty( landlordId: string, propertyId: string, ): Promise { if (isE2EEnabled()) { return getE2ETwitterInviteByProperty(landlordId, propertyId); } const result = await db.select().from(tenant_invites).where( and( eq(tenant_invites.property_id, propertyId), eq(tenant_invites.landlord_id, landlordId), ), ); return result[0] || null; } export async function updateTenantInvite( inviteId: string, updates: Partial, ) { if (isE2EEnabled()) { const updated = updateE2ETwitterInvite(inviteId, updates); return updated ? [updated] : []; } const now = Date.now(); return await db.update(tenant_invites) .set({ ...updates, updated_on: now }) .where(eq(tenant_invites.id, inviteId)) .returning(); } export async function getTenantInvitesByEmail(email: string) { const normalizedEmail = email.trim().toLowerCase(); if (!normalizedEmail) return []; if (isE2EEnabled()) { return getE2ETwitterInvitesByEmail(normalizedEmail); } const pattern = `%${normalizedEmail}%`; const result = await db.select().from(tenant_invites).where( ilike(tenant_invites.invited_emails, pattern), ); return result.filter((invite) => hasMatchingInviteEmail(invite, normalizedEmail) ); } export async function removeTenantInviteEmail(inviteId: string, email: string) { const normalizedEmail = normalizeInviteEmail(email); if (!normalizedEmail) { return { removed: false, invite: null }; } const invite = await getTenantInviteById(inviteId); if (!invite) return { removed: false, invite: null }; const existingEmails = parseInviteEmails(invite.invited_emails); const filteredEmails = existingEmails.filter((entry) => entry.toLowerCase() !== normalizedEmail ); if (filteredEmails.length === existingEmails.length) { return { removed: false, invite }; } const updated = await updateTenantInvite(inviteId, { invited_emails: JSON.stringify(filteredEmails), }); return { removed: true, invite: updated[0] ?? invite }; } export async function getTenantInvitesByLandlord(landlordId: string): Promise { if (isE2EEnabled()) { return getE2ETenantInvitesByLandlord(landlordId); } const result = await db .select() .from(tenant_invites) .where(eq(tenant_invites.landlord_id, landlordId)) .orderBy(desc(tenant_invites.created_on)); return result; } export async function deleteTenantInvite(inviteId: string): Promise { if (isE2EEnabled()) { deleteE2ETenantInvite(inviteId); return; } await db .delete(tenant_invites) .where(eq(tenant_invites.id, inviteId)); }