/** * Organization Model * Shared across all projects - tracks companies, institutions, media outlets */ const { ObjectId } = require('mongodb'); const { getCollection } = require('../utils/db.util'); class Organization { /** * Create a new organization */ static async create(data) { const collection = await getCollection('organizations'); const organization = { name: data.name, domain: data.domain || null, // Primary domain (e.g., "economist.com") type: data.type || 'other', // media, academic, corporate, nonprofit, government, other country: data.country || null, // Contact information contact_info: { website: data.contact_info?.website || null, email: data.contact_info?.email || null, phone: data.contact_info?.phone || null, address: data.contact_info?.address || null }, // Metadata metadata: { industry: data.metadata?.industry || null, size: data.metadata?.size || null, // small, medium, large description: data.metadata?.description || null, notes: data.metadata?.notes || null }, // Projects this organization has interacted with projects: data.projects || [], // ['tractatus', 'family-history', 'sydigital'] // Relationship status relationship: { status: data.relationship?.status || 'prospect', // prospect, active, partner, archived since: data.relationship?.since || new Date(), tier: data.relationship?.tier || 'standard' // standard, priority, strategic }, // Social/web presence social: { linkedin: data.social?.linkedin || null, twitter: data.social?.twitter || null, other: data.social?.other || {} }, created_at: new Date(), updated_at: new Date() }; const result = await collection.insertOne(organization); return { ...organization, _id: result.insertedId }; } /** * Find organization by ID */ static async findById(id) { const collection = await getCollection('organizations'); return await collection.findOne({ _id: new ObjectId(id) }); } /** * Find organization by name (case-insensitive) */ static async findByName(name) { const collection = await getCollection('organizations'); return await collection.findOne({ name: { $regex: new RegExp(`^${name}$`, 'i') } }); } /** * Find organization by domain */ static async findByDomain(domain) { const collection = await getCollection('organizations'); return await collection.findOne({ domain }); } /** * Find or create organization */ static async findOrCreate(data) { // Try to find by domain first (most reliable) if (data.domain) { const existing = await this.findByDomain(data.domain); if (existing) return existing; } // Try to find by name if (data.name) { const existing = await this.findByName(data.name); if (existing) return existing; } // Create new organization return await this.create(data); } /** * List organizations with filtering */ static async list(filters = {}, options = {}) { const collection = await getCollection('organizations'); const { limit = 50, skip = 0 } = options; const query = {}; if (filters.type) query.type = filters.type; if (filters.country) query.country = filters.country; if (filters.project) query.projects = filters.project; if (filters.status) query['relationship.status'] = filters.status; if (filters.tier) query['relationship.tier'] = filters.tier; return await collection .find(query) .sort({ name: 1 }) .skip(skip) .limit(limit) .toArray(); } /** * Search organizations by name or domain */ static async search(searchTerm, options = {}) { const collection = await getCollection('organizations'); const { limit = 20 } = options; return await collection .find({ $or: [ { name: { $regex: searchTerm, $options: 'i' } }, { domain: { $regex: searchTerm, $options: 'i' } }, { 'contact_info.website': { $regex: searchTerm, $options: 'i' } } ] }) .limit(limit) .toArray(); } /** * Update organization */ static async update(id, data) { const collection = await getCollection('organizations'); const updateData = { ...data, updated_at: new Date() }; await collection.updateOne( { _id: new ObjectId(id) }, { $set: updateData } ); return await this.findById(id); } /** * Add project to organization */ static async addProject(id, projectName) { const collection = await getCollection('organizations'); await collection.updateOne( { _id: new ObjectId(id) }, { $addToSet: { projects: projectName }, $set: { updated_at: new Date() } } ); return await this.findById(id); } /** * Delete organization */ static async delete(id) { const collection = await getCollection('organizations'); return await collection.deleteOne({ _id: new ObjectId(id) }); } /** * Get statistics */ static async getStats() { const collection = await getCollection('organizations'); const [total, byType, byStatus, byProject] = await Promise.all([ collection.countDocuments(), collection.aggregate([ { $group: { _id: '$type', count: { $sum: 1 } } } ]).toArray(), collection.aggregate([ { $group: { _id: '$relationship.status', count: { $sum: 1 } } } ]).toArray(), collection.aggregate([ { $unwind: '$projects' }, { $group: { _id: '$projects', count: { $sum: 1 } } } ]).toArray() ]); return { total, by_type: byType.reduce((acc, item) => { acc[item._id] = item.count; return acc; }, {}), by_status: byStatus.reduce((acc, item) => { acc[item._id] = item.count; return acc; }, {}), by_project: byProject.reduce((acc, item) => { acc[item._id] = item.count; return acc; }, {}) }; } } module.exports = Organization;