276 lines
6.8 KiB
TypeScript
276 lines
6.8 KiB
TypeScript
// services/notificationService.ts
|
|
// Handle notifications for lease approval workflow
|
|
|
|
import { eq } from "drizzle-orm";
|
|
import { db } from "../db/db.ts";
|
|
import { users } from "../db/schema.ts";
|
|
|
|
export interface NotificationPreferences {
|
|
email_messages?: boolean;
|
|
email_reminders?: boolean;
|
|
email_payments?: boolean;
|
|
email_lease_approvals?: boolean;
|
|
}
|
|
|
|
export interface LeaseApprovalNotificationData {
|
|
lease_id: string;
|
|
property_address?: string;
|
|
tenant_name?: string;
|
|
landlord_name?: string;
|
|
monthly_rent?: number;
|
|
}
|
|
|
|
export interface NotificationResult {
|
|
success: boolean;
|
|
sent_via: string[];
|
|
skipped: string[];
|
|
error?: string;
|
|
}
|
|
|
|
function parseNotificationPreferences(
|
|
preferencesJson: string | null,
|
|
): NotificationPreferences {
|
|
if (!preferencesJson) {
|
|
return {
|
|
email_messages: true,
|
|
email_reminders: true,
|
|
email_payments: true,
|
|
email_lease_approvals: true,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(preferencesJson);
|
|
return {
|
|
email_messages: parsed.email_messages ?? true,
|
|
email_reminders: parsed.email_reminders ?? true,
|
|
email_payments: parsed.email_payments ?? true,
|
|
email_lease_approvals: parsed.email_lease_approvals ?? true,
|
|
};
|
|
} catch {
|
|
return {
|
|
email_messages: true,
|
|
email_reminders: true,
|
|
email_payments: true,
|
|
email_lease_approvals: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
async function getUserNotificationPreferences(
|
|
userId: string,
|
|
): Promise<NotificationPreferences> {
|
|
const result = await db.select()
|
|
.from(users)
|
|
.where(eq(users.id, userId));
|
|
|
|
if (result.length === 0) {
|
|
return {
|
|
email_messages: true,
|
|
email_reminders: true,
|
|
email_payments: true,
|
|
email_lease_approvals: true,
|
|
};
|
|
}
|
|
|
|
return parseNotificationPreferences(
|
|
result[0].notification_preferences_json as string | null,
|
|
);
|
|
}
|
|
|
|
async function getUserEmail(userId: string): Promise<string | null> {
|
|
const result = await db.select()
|
|
.from(users)
|
|
.where(eq(users.id, userId));
|
|
|
|
return result[0]?.email || null;
|
|
}
|
|
|
|
function buildEmailSubject(
|
|
type: "approval_request" | "approved" | "rejected",
|
|
): string {
|
|
switch (type) {
|
|
case "approval_request":
|
|
return "New Lease Approval Required";
|
|
case "approved":
|
|
return "Lease Approved - Congratulations!";
|
|
case "rejected":
|
|
return "Lease Request Update";
|
|
default:
|
|
return "Lease Notification";
|
|
}
|
|
}
|
|
|
|
function buildEmailBody(
|
|
type: "approval_request" | "approved" | "rejected",
|
|
data: LeaseApprovalNotificationData,
|
|
): string {
|
|
const { property_address, tenant_name, landlord_name, monthly_rent } = data;
|
|
|
|
switch (type) {
|
|
case "approval_request":
|
|
return `
|
|
Dear ${landlord_name || "Landlord"},
|
|
|
|
A new lease approval request has been submitted for your property.
|
|
|
|
Property: ${property_address || "N/A"}
|
|
Tenant: ${tenant_name || "N/A"}
|
|
Monthly Rent: ${monthly_rent ? `$${monthly_rent.toLocaleString()}` : "N/A"}
|
|
|
|
Please review this request in your landlord dashboard and either approve or reject the lease.
|
|
|
|
You can view and manage pending approvals by visiting your dashboard.
|
|
|
|
Best regards,
|
|
The Property Management Team
|
|
`.trim();
|
|
|
|
case "approved":
|
|
return `
|
|
Dear ${tenant_name || "Tenant"},
|
|
|
|
Great news! Your lease request has been approved.
|
|
|
|
Property: ${property_address || "N/A"}
|
|
Landlord: ${landlord_name || "N/A"}
|
|
|
|
Your lease is now active. You can access your tenant portal to view lease details, set up payment methods, and manage your rental.
|
|
|
|
Congratulations on your new lease!
|
|
|
|
Best regards,
|
|
The Property Management Team
|
|
`.trim();
|
|
|
|
case "rejected":
|
|
return `
|
|
Dear ${tenant_name || "Tenant"},
|
|
|
|
We regret to inform you that your lease request has been declined.
|
|
|
|
Property: ${property_address || "N/A"}
|
|
Landlord: ${landlord_name || "N/A"}
|
|
|
|
If you have questions about this decision or would like to discuss it further, please contact your landlord directly.
|
|
|
|
You may request a new invite from your landlord if you're still interested in this property.
|
|
|
|
Best regards,
|
|
The Property Management Team
|
|
`.trim();
|
|
|
|
default:
|
|
return "You have received a notification regarding your lease request.";
|
|
}
|
|
}
|
|
|
|
function sendEmail(
|
|
to: string,
|
|
subject: string,
|
|
body: string,
|
|
): boolean {
|
|
try {
|
|
console.log(`[Notification] Sending email to: ${to}`);
|
|
console.log(`[Notification] Subject: ${subject}`);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`[Notification] Failed to send email to ${to}:`, error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function sendLeaseApprovalRequest(
|
|
landlordId: string,
|
|
data: LeaseApprovalNotificationData,
|
|
): Promise<NotificationResult> {
|
|
const preferences = await getUserNotificationPreferences(landlordId);
|
|
const email = await getUserEmail(landlordId);
|
|
|
|
const sent_via: string[] = [];
|
|
const skipped: string[] = [];
|
|
|
|
if (preferences.email_lease_approvals && email) {
|
|
const subject = buildEmailSubject("approval_request");
|
|
const body = buildEmailBody("approval_request", data);
|
|
const success = await sendEmail(email, subject, body);
|
|
|
|
if (success) {
|
|
sent_via.push("email");
|
|
} else {
|
|
skipped.push("email (failed)");
|
|
}
|
|
} else {
|
|
skipped.push("email (disabled or no email)");
|
|
}
|
|
|
|
return {
|
|
success: sent_via.length > 0,
|
|
sent_via,
|
|
skipped,
|
|
};
|
|
}
|
|
|
|
export async function sendLeaseApproved(
|
|
tenantId: string,
|
|
data: LeaseApprovalNotificationData,
|
|
): Promise<NotificationResult> {
|
|
const preferences = await getUserNotificationPreferences(tenantId);
|
|
const email = await getUserEmail(tenantId);
|
|
|
|
const sent_via: string[] = [];
|
|
const skipped: string[] = [];
|
|
|
|
if (preferences.email_lease_approvals && email) {
|
|
const subject = buildEmailSubject("approved");
|
|
const body = buildEmailBody("approved", data);
|
|
const success = await sendEmail(email, subject, body);
|
|
|
|
if (success) {
|
|
sent_via.push("email");
|
|
} else {
|
|
skipped.push("email (failed)");
|
|
}
|
|
} else {
|
|
skipped.push("email (disabled or no email)");
|
|
}
|
|
|
|
return {
|
|
success: sent_via.length > 0,
|
|
sent_via,
|
|
skipped,
|
|
};
|
|
}
|
|
|
|
export async function sendLeaseRejected(
|
|
tenantId: string,
|
|
data: LeaseApprovalNotificationData,
|
|
): Promise<NotificationResult> {
|
|
const preferences = await getUserNotificationPreferences(tenantId);
|
|
const email = await getUserEmail(tenantId);
|
|
|
|
const sent_via: string[] = [];
|
|
const skipped: string[] = [];
|
|
|
|
if (preferences.email_lease_approvals && email) {
|
|
const subject = buildEmailSubject("rejected");
|
|
const body = buildEmailBody("rejected", data);
|
|
const success = await sendEmail(email, subject, body);
|
|
|
|
if (success) {
|
|
sent_via.push("email");
|
|
} else {
|
|
skipped.push("email (failed)");
|
|
}
|
|
} else {
|
|
skipped.push("email (disabled or no email)");
|
|
}
|
|
|
|
return {
|
|
success: sent_via.length > 0,
|
|
sent_via,
|
|
skipped,
|
|
};
|
|
}
|