Files
inbox-negotiator/src/lib/emailVariables.ts
2025-08-25 21:18:55 +00:00

244 lines
7.1 KiB
TypeScript

/**
* Email Variables Utility Module
*
* This module provides functions for handling email template variables:
* - Extracting variables from text in {{ variable }} format
* - Replacing variables with their values
* - Loading and saving variables from/to the database
* - Processing complete email templates
*/
import { databases, DATABASE_ID, COLLECTIONS } from "./appwrite-admin";
import { ID } from "appwrite";
export interface VariableProcessingResult {
processedSubject: string;
processedBody: string;
hasUnfilledVariables: boolean;
unfilledVariables: string[];
}
/**
* Extract variables from text in {{ variable }} format
* @param text - The text to extract variables from
* @returns Array of unique variable names found in the text
*/
export function extractVariables(text: string): string[] {
const variableRegex = /\{\{\s*([^}]+)\s*\}\}/g;
const matches: string[] = [];
let match;
while ((match = variableRegex.exec(text)) !== null) {
const variableName = match[1].trim();
if (!matches.includes(variableName)) {
matches.push(variableName);
}
}
return matches;
}
/**
* Replace variables in text with their values
* @param text - The text containing variable placeholders
* @param variables - Object mapping variable names to their values
* @returns Text with variables replaced by their values
*/
export function replaceVariables(
text: string,
variables: Record<string, string>
): string {
let result = text;
Object.entries(variables).forEach(([key, value]) => {
// Escape special regex characters in the variable name
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`\\{\\{\\s*${escapedKey}\\s*\\}\\}`, "g");
result = result.replace(regex, value);
});
return result;
}
/**
* Load variables from database for a specific debt
* @param debtId - The ID of the debt record
* @returns Object mapping variable names to their values
*/
export async function loadVariablesFromDatabase(
debtId: string
): Promise<Record<string, string>> {
try {
const response = await databases.listDocuments(
DATABASE_ID,
COLLECTIONS.DEBT_VARIABLES,
[`debt_id="${debtId}"`]
);
const loadedVariables: Record<string, string> = {};
response.documents.forEach((dbVar: any) => {
loadedVariables[dbVar.variable_name] = dbVar.variable_value || "";
});
return loadedVariables;
} catch (error) {
console.error("Error in loadVariablesFromDatabase:", error);
return {};
}
}
/**
* Save variables to database for a specific debt
* @param debtId - The ID of the debt record
* @param variables - Object mapping variable names to their values
*/
export async function saveVariablesToDatabase(
debtId: string,
variables: Record<string, string>
): Promise<void> {
try {
// First, get existing variables for this debt
const existingVariables = await databases.listDocuments(
DATABASE_ID,
COLLECTIONS.DEBT_VARIABLES,
[`debt_id="${debtId}"`]
);
// Delete existing variables
for (const variable of existingVariables.documents) {
await databases.deleteDocument(
DATABASE_ID,
COLLECTIONS.DEBT_VARIABLES,
variable.$id
);
}
// Insert new variables
for (const [name, value] of Object.entries(variables)) {
await databases.createDocument(
DATABASE_ID,
COLLECTIONS.DEBT_VARIABLES,
ID.unique(),
{
debt_id: debtId,
variable_name: name,
variable_value: value,
}
);
}
} catch (error) {
console.error("Error in saveVariablesToDatabase:", error);
throw error;
}
}
/**
* Process email template by extracting variables, loading values, and replacing placeholders
* @param debtId - The ID of the debt record
* @param subject - The email subject template
* @param body - The email body template
* @returns Object containing processed subject/body and unfilled variable information
*/
export async function processEmailTemplate(
debtId: string,
subject: string,
body: string
): Promise<VariableProcessingResult> {
try {
// Extract all variables from subject and body
const allText = `${subject} ${body}`;
const extractedVars = extractVariables(allText);
// Load saved variables from database
const savedVariables = await loadVariablesFromDatabase(debtId);
// Check which variables don't have values
const unfilledVariables = extractedVars.filter(
variable => !savedVariables[variable] || savedVariables[variable].trim() === ""
);
const hasUnfilledVariables = unfilledVariables.length > 0;
// Replace variables in subject and body
const processedSubject = replaceVariables(subject, savedVariables);
const processedBody = replaceVariables(body, savedVariables);
return {
processedSubject,
processedBody,
hasUnfilledVariables,
unfilledVariables,
};
} catch (error) {
console.error("Error in processEmailTemplate:", error);
throw error;
}
}
/**
* Get all variables from subject and body text, merging with saved values
* @param debtId - The ID of the debt record
* @param subject - The email subject template
* @param body - The email body template
* @returns Object mapping variable names to their values (empty string if not saved)
*/
export async function getVariablesForTemplate(
debtId: string,
subject: string,
body: string
): Promise<Record<string, string>> {
try {
// Extract variables from both subject and body
const allText = `${subject} ${body}`;
const extractedVars = extractVariables(allText);
// Load saved variables from database
const savedVariables = await loadVariablesFromDatabase(debtId);
// Merge extracted variables with saved values
const variables: Record<string, string> = {};
extractedVars.forEach((variable) => {
variables[variable] = savedVariables[variable] || "";
});
return variables;
} catch (error) {
console.error("Error in getVariablesForTemplate:", error);
return {};
}
}
/**
* Update variables when template text changes
* @param currentVariables - Current variable values
* @param newText - New template text
* @param otherText - Other template text (e.g., if updating body, pass subject here)
* @returns Updated variables object
*/
export function updateVariablesForTextChange(
currentVariables: Record<string, string>,
newText: string,
otherText: string = ""
): Record<string, string> {
// Extract variables from the new text and other text
const allText = `${newText} ${otherText}`;
const newVariables = extractVariables(allText);
const updatedVariables = { ...currentVariables };
// Add new variables if they don't exist
newVariables.forEach((variable) => {
if (!(variable in updatedVariables)) {
updatedVariables[variable] = "";
}
});
// Remove variables that no longer exist in any text
Object.keys(updatedVariables).forEach((variable) => {
if (!newVariables.includes(variable)) {
delete updatedVariables[variable];
}
});
return updatedVariables;
}