/** * Contact Model * General contact form submissions (non-media, non-case) * Foundation for unified CRM system */ const { ObjectId } = require('mongodb'); const { getCollection } = require('../utils/db.util'); class Contact { /** * Create a new contact submission */ static async create(data) { const collection = await getCollection('contacts'); const contact = { type: data.type || 'general', // general, partnership, technical, feedback contact: { name: data.contact.name, email: data.contact.email, organization: data.contact.organization || null, phone: data.contact.phone || null }, inquiry: { subject: data.inquiry.subject || null, message: data.inquiry.message }, source: data.source || 'footer_contact', // Track where submission came from status: data.status || 'new', // new, assigned, responded, closed priority: data.priority || 'normal', // low, normal, high assigned_to: data.assigned_to || null, metadata: { user_agent: data.metadata?.user_agent, ip: data.metadata?.ip, source_page: data.metadata?.source_page, referrer: data.metadata?.referrer }, response: { sent_at: data.response?.sent_at || null, content: data.response?.content || null, responder: data.response?.responder || null }, created_at: new Date(), updated_at: new Date() }; const result = await collection.insertOne(contact); return { ...contact, _id: result.insertedId }; } /** * Find contact by ID */ static async findById(id) { const collection = await getCollection('contacts'); return await collection.findOne({ _id: new ObjectId(id) }); } /** * Find contacts by status */ static async findByStatus(status, options = {}) { const collection = await getCollection('contacts'); const { limit = 20, skip = 0 } = options; return await collection .find({ status }) .sort({ created_at: -1 }) .skip(skip) .limit(limit) .toArray(); } /** * Find contacts by type */ static async findByType(type, options = {}) { const collection = await getCollection('contacts'); const { limit = 20, skip = 0 } = options; return await collection .find({ type }) .sort({ created_at: -1 }) .skip(skip) .limit(limit) .toArray(); } /** * List all contacts with filtering */ static async list(filters = {}, options = {}) { const collection = await getCollection('contacts'); const { limit = 20, skip = 0 } = options; const query = {}; if (filters.status) query.status = filters.status; if (filters.type) query.type = filters.type; if (filters.priority) query.priority = filters.priority; if (filters.assigned_to) query.assigned_to = new ObjectId(filters.assigned_to); return await collection .find(query) .sort({ created_at: -1 }) .skip(skip) .limit(limit) .toArray(); } /** * Count contacts by status */ static async countByStatus(status) { const collection = await getCollection('contacts'); return await collection.countDocuments({ status }); } /** * Update contact */ static async update(id, data) { const collection = await getCollection('contacts'); const updateData = { ...data, updated_at: new Date() }; await collection.updateOne( { _id: new ObjectId(id) }, { $set: updateData } ); return await this.findById(id); } /** * Assign contact to user */ static async assign(id, userId) { return await this.update(id, { assigned_to: userId ? new ObjectId(userId) : null, status: 'assigned' }); } /** * Mark contact as responded */ static async markResponded(id, responseData) { return await this.update(id, { status: 'responded', response: { sent_at: new Date(), content: responseData.content, responder: responseData.responder } }); } /** * Delete contact */ static async delete(id) { const collection = await getCollection('contacts'); return await collection.deleteOne({ _id: new ObjectId(id) }); } /** * Get statistics */ static async getStats() { const collection = await getCollection('contacts'); const [total, newCount, assignedCount, respondedCount, closedCount] = await Promise.all([ collection.countDocuments(), collection.countDocuments({ status: 'new' }), collection.countDocuments({ status: 'assigned' }), collection.countDocuments({ status: 'responded' }), collection.countDocuments({ status: 'closed' }) ]); // Type breakdown const typeBreakdown = await collection.aggregate([ { $group: { _id: '$type', count: { $sum: 1 } } } ]).toArray(); return { total, by_status: { new: newCount, assigned: assignedCount, responded: respondedCount, closed: closedCount }, by_type: typeBreakdown.reduce((acc, item) => { acc[item._id] = item.count; return acc; }, {}) }; } } module.exports = Contact;