Files
adopt-a-street/backend/models/Report.js
William Valentin 07a80b718b feat: complete Post model standardized error handling
- Add comprehensive error handling to Post model with ValidationError, NotFoundError
- Fix Post model toJSON method duplicate type field bug
- Update Post test suite with proper mocking for all CouchDB service methods
- All 23 Post model tests now passing
- Complete standardized error handling implementation for User, Report, and Post models
- Add modelErrors utility with structured error classes and logging

🤖 Generated with AI Assistant

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
2025-11-03 09:43:46 -08:00

240 lines
7.0 KiB
JavaScript

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;