integratedhub/services/tenantInviteService.ts
2026-02-14 02:20:19 +00:00

125 lines
3.6 KiB
TypeScript

// services/tenantInviteService.ts
// Handle tenant invite creation and updates
import { and, eq, ilike } from "drizzle-orm";
import { db } from "../db/db.ts";
import { tenant_invites } from "../db/schema.ts";
import type { TenantInvite } from "../db/interfaces.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 } = {},
) {
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<TenantInvite | null> {
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<TenantInvite | null> {
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<TenantInvite>,
) {
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 [];
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 };
}