249 lines
7.2 KiB
TypeScript
249 lines
7.2 KiB
TypeScript
/**
|
|
* Recipient Service - Platform-aware recipient management
|
|
* Uses Firebase abstraction layer for cross-platform support
|
|
*/
|
|
import { doc, collection } from '../firebase';
|
|
|
|
export interface Recipient {
|
|
id: string;
|
|
uid: string;
|
|
fullName: string;
|
|
phoneNumber: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
isActive: boolean;
|
|
}
|
|
|
|
export class RecipientService {
|
|
private static snapshotExists(docSnap: any): boolean {
|
|
const existsValue = docSnap?.exists;
|
|
if (typeof existsValue === 'function') {
|
|
try {
|
|
return !!existsValue.call(docSnap);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
return !!existsValue;
|
|
}
|
|
|
|
// Add a new recipient
|
|
static async addRecipient(
|
|
uid: string,
|
|
recipientData: {
|
|
fullName: string;
|
|
phoneNumber: string;
|
|
}
|
|
): Promise<{ success: boolean; error?: string; recipientId?: string }> {
|
|
try {
|
|
// Validate input
|
|
if (!recipientData.fullName.trim()) {
|
|
return { success: false, error: 'Full name is required' };
|
|
}
|
|
|
|
if (!recipientData.phoneNumber.trim()) {
|
|
return { success: false, error: 'Phone number is required' };
|
|
}
|
|
|
|
// Basic phone number validation
|
|
const cleanPhone = recipientData.phoneNumber.replace(/[^+\d]/g, '');
|
|
if (cleanPhone.length < 7) {
|
|
return { success: false, error: 'Please enter a valid phone number' };
|
|
}
|
|
|
|
// Check if recipient already exists
|
|
const existingRecipient = await RecipientService.getRecipientByPhone(uid, cleanPhone);
|
|
if (existingRecipient) {
|
|
return { success: false, error: 'This recipient already exists' };
|
|
}
|
|
|
|
// Generate recipient ID
|
|
const recipientId = `recipient_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
const newRecipient: Recipient = {
|
|
id: recipientId,
|
|
uid,
|
|
fullName: recipientData.fullName.trim(),
|
|
phoneNumber: cleanPhone,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
isActive: true,
|
|
};
|
|
|
|
// Save to Firestore
|
|
const recipientRef = doc('recipients', recipientId);
|
|
await recipientRef.set(newRecipient);
|
|
|
|
return { success: true, recipientId };
|
|
} catch (error) {
|
|
console.error('Add recipient error:', error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to add recipient'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Get all recipients for a user
|
|
static async getUserRecipients(uid: string): Promise<Recipient[]> {
|
|
try {
|
|
const recipientsCollection = collection('recipients');
|
|
const querySnapshot = await recipientsCollection
|
|
.where('uid', '==', uid)
|
|
.where('isActive', '==', true)
|
|
.get();
|
|
|
|
const recipients: Recipient[] = [];
|
|
|
|
querySnapshot.forEach((docData: any) => {
|
|
recipients.push(docData.data() as Recipient);
|
|
});
|
|
|
|
// Sort by creation date (newest first)
|
|
recipients.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
|
|
return recipients;
|
|
} catch (error) {
|
|
console.error('Error fetching user recipients:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Get recipient by phone number for a specific user
|
|
static async getRecipientByPhone(uid: string, phoneNumber: string): Promise<Recipient | null> {
|
|
try {
|
|
const recipientsCollection = collection('recipients');
|
|
const querySnapshot = await recipientsCollection
|
|
.where('uid', '==', uid)
|
|
.where('phoneNumber', '==', phoneNumber)
|
|
.where('isActive', '==', true)
|
|
.get();
|
|
|
|
if (querySnapshot.empty) {
|
|
return null;
|
|
}
|
|
|
|
return querySnapshot.docs[0].data() as Recipient;
|
|
} catch (error) {
|
|
console.error('Error fetching recipient by phone:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Update recipient
|
|
static async updateRecipient(
|
|
recipientId: string,
|
|
updates: {
|
|
fullName?: string;
|
|
phoneNumber?: string;
|
|
}
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const recipientRef = doc('recipients', recipientId);
|
|
const recipientDoc = await recipientRef.get();
|
|
|
|
if (!RecipientService.snapshotExists(recipientDoc)) {
|
|
return { success: false, error: 'Recipient not found' };
|
|
}
|
|
|
|
const updateData: any = {
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
if (updates.fullName) {
|
|
updateData.fullName = updates.fullName.trim();
|
|
}
|
|
|
|
if (updates.phoneNumber) {
|
|
const cleanPhone = updates.phoneNumber.replace(/[^+\d]/g, '');
|
|
if (cleanPhone.length < 7) {
|
|
return { success: false, error: 'Please enter a valid phone number' };
|
|
}
|
|
updateData.phoneNumber = cleanPhone;
|
|
}
|
|
|
|
await recipientRef.update(updateData);
|
|
|
|
console.log('Recipient updated successfully');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Update recipient error:', error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to update recipient'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Delete recipient (soft delete)
|
|
static async deleteRecipient(recipientId: string): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const recipientRef = doc('recipients', recipientId);
|
|
|
|
await recipientRef.update({
|
|
isActive: false,
|
|
updatedAt: new Date(),
|
|
});
|
|
|
|
console.log('Recipient deleted successfully');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Delete recipient error:', error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to delete recipient'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Hard delete recipient (permanent)
|
|
static async permanentDeleteRecipient(recipientId: string): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const recipientRef = doc('recipients', recipientId);
|
|
await recipientRef.delete();
|
|
|
|
console.log('Recipient permanently deleted');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Permanent delete recipient error:', error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to permanently delete recipient'
|
|
};
|
|
}
|
|
}
|
|
|
|
static async getRecipientById(recipientId: string): Promise<{ success: boolean; recipient?: Recipient; error?: string }> {
|
|
try {
|
|
const recipientRef = doc('recipients', recipientId);
|
|
const docSnap = await recipientRef.get();
|
|
|
|
if (!RecipientService.snapshotExists(docSnap)) {
|
|
return { success: false, error: 'Recipient not found' };
|
|
}
|
|
|
|
const data = docSnap.data() as Recipient | undefined;
|
|
if (!data) {
|
|
return { success: false, error: 'Recipient data unavailable' };
|
|
}
|
|
|
|
const recipient: Recipient = {
|
|
...data,
|
|
id: data.id ?? recipientId,
|
|
createdAt: (data.createdAt as any)?.toDate?.() ?? data.createdAt ?? new Date(),
|
|
updatedAt: (data.updatedAt as any)?.toDate?.() ?? data.updatedAt ?? new Date(),
|
|
};
|
|
|
|
return { success: true, recipient };
|
|
} catch (error) {
|
|
console.error('Error fetching recipient by id:', error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to fetch recipient',
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export default RecipientService;
|