- 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>
240 lines
7.0 KiB
JavaScript
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;
|