const couchdbService = require("../services/couchdbService"); const { ValidationError, NotFoundError, DatabaseError, withErrorHandling, createErrorContext } = require("../utils/modelErrors"); class Report { constructor(data) { // Handle both new documents and database documents const isNew = !data._id; // For new documents, validate required fields if (isNew) { if (!data.street) { throw new ValidationError('Street is required', 'street', data.street); } if (!data.reporter) { throw new ValidationError('Reporter is required', 'reporter', data.reporter); } if (!data.type) { throw new ValidationError('Type is required', 'type', data.type); } if (!data.description) { throw new ValidationError('Description is required', 'description', data.description); } // Validate report type const validTypes = ['pothole', 'graffiti', 'trash', 'broken_light', 'other']; if (!validTypes.includes(data.type)) { throw new ValidationError('Invalid report type', 'type', data.type); } } this._id = data._id || null; this._rev = data._rev || null; this.type = data.type || "report"; // Keep original type for database docs this.street = data.street; this.reporter = data.reporter; this.reportType = data.type; this.description = data.description; this.status = data.status || "open"; this.imageUrl = data.imageUrl || null; this.cloudinaryPublicId = data.cloudinaryPublicId || null; this.location = data.location || null; this.createdAt = data.createdAt || new Date().toISOString(); this.updatedAt = data.updatedAt || new Date().toISOString(); } static async create(reportData) { const errorContext = createErrorContext('Report', 'create', { reportData: { ...reportData, description: reportData.description?.substring(0, 100) + '...' } }); return await withErrorHandling(async () => { const report = new Report(reportData); return await couchdbService.createDocument(report.toJSON()); }, errorContext); } static async findById(id) { const errorContext = createErrorContext('Report', 'findById', { id }); return await withErrorHandling(async () => { const doc = await couchdbService.getDocument(id); if (doc && (doc.type === "report" || ['pothole', 'graffiti', 'trash', 'broken_light', 'other'].includes(doc.type))) { return new Report(doc); } return null; }, errorContext); } static async find(filter = {}) { const errorContext = createErrorContext('Report', 'find', { filter }); return await withErrorHandling(async () => { const selector = { type: "report", ...filter, }; return await couchdbService.findDocuments(selector); }, errorContext); } static async findWithPagination(options = {}) { const { page = 1, limit = 10, sort = { createdAt: -1 } } = options; const errorContext = createErrorContext('Report', 'findWithPagination', { options }); return await withErrorHandling(async () => { const selector = { type: "report" }; return await couchdbService.findWithPagination(selector, { page, limit, sort, }); }, errorContext); } static async update(id, updateData) { const errorContext = createErrorContext('Report', 'update', { id, updateData }); return await withErrorHandling(async () => { const doc = await couchdbService.getDocument(id); if (!doc || doc.type !== "report") { throw new NotFoundError('Report', id); } const updatedDoc = { ...doc, ...updateData, updatedAt: new Date().toISOString(), }; return await couchdbService.updateDocument(id, updatedDoc); }, errorContext); } static async delete(id) { const errorContext = createErrorContext('Report', 'delete', { id }); return await withErrorHandling(async () => { const doc = await couchdbService.getDocument(id); if (!doc || doc.type !== "report") { throw new NotFoundError('Report', id); } return await couchdbService.deleteDocument(id, doc._rev); }, errorContext); } static async countDocuments(filter = {}) { const errorContext = createErrorContext('Report', 'countDocuments', { filter }); return await withErrorHandling(async () => { const selector = { type: "report", ...filter, }; return await couchdbService.countDocuments(selector); }, errorContext); } static async findByStreet(streetId) { const errorContext = createErrorContext('Report', 'findByStreet', { streetId }); return await withErrorHandling(async () => { const selector = { type: "report", "street._id": streetId, }; return await couchdbService.findDocuments(selector); }, errorContext); } static async findByUser(userId) { const errorContext = createErrorContext('Report', 'findByUser', { userId }); return await withErrorHandling(async () => { const selector = { type: "report", "user._id": userId, }; return await couchdbService.findDocuments(selector); }, errorContext); } static async findByStatus(status) { const errorContext = createErrorContext('Report', 'findByStatus', { status }); return await withErrorHandling(async () => { const selector = { type: "report", status, }; return await couchdbService.findDocuments(selector); }, errorContext); } static async update(id, updateData) { const errorContext = createErrorContext('Report', 'update', { id, updateData }); return await withErrorHandling(async () => { return await couchdbService.update(id, updateData); }, errorContext); } // Convert to CouchDB document format toJSON() { return { _id: this._id, _rev: this._rev, type: this.type, street: this.street, reporter: this.reporter, type: this.reportType, description: this.description, status: this.status, imageUrl: this.imageUrl, cloudinaryPublicId: this.cloudinaryPublicId, location: this.location, createdAt: this.createdAt, updatedAt: this.updatedAt }; } // Instance save method async save() { const errorContext = createErrorContext('Report', 'save', { id: this._id, isNew: !this._id }); return await withErrorHandling(async () => { if (!this._id) { // New document this._id = `report_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const created = await couchdbService.createDocument(this.toJSON()); this._rev = created._rev; return this; } else { // Update existing document this.updatedAt = new Date().toISOString(); const updated = await couchdbService.updateDocument(this.toJSON()); this._rev = updated._rev; return this; } }, errorContext); } } module.exports = Report;