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>
This commit is contained in:
William Valentin
2025-11-03 09:43:46 -08:00
parent 97f794fca5
commit 07a80b718b
6 changed files with 1044 additions and 339 deletions

View File

@@ -1,182 +1,311 @@
const couchdbService = require("../services/couchdbService");
const {
ValidationError,
NotFoundError,
DatabaseError,
withErrorHandling,
createErrorContext
} = require("../utils/modelErrors");
class Post {
static async create(postData) {
const { user, content, imageUrl, cloudinaryPublicId } = postData;
constructor(data) {
// Handle both new documents and database documents
const isNew = !data._id;
// Get user data for embedding
const userDoc = await couchdbService.findUserById(user);
if (!userDoc) {
throw new Error("User not found");
// For new documents, validate required fields
if (isNew) {
if (!data.user) {
throw new ValidationError('User is required', 'user', data.user);
}
// Note: Original behavior allows posts without content, so we don't validate content here
// The validation will happen at route level if needed
// Validate post type
const validTypes = ['text', 'image', 'achievement'];
if (data.type && !validTypes.includes(data.type)) {
throw new ValidationError('Invalid post type', 'type', data.type);
}
}
const post = {
_id: `post_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
type: "post",
user: {
this._id = data._id || null;
this._rev = data._rev || null;
this.type = data.type || "post"; // Keep original type for database docs
this.user = data.user;
this.content = data.content;
this.imageUrl = data.imageUrl || null;
this.cloudinaryPublicId = data.cloudinaryPublicId || null;
this.postType = data.type || 'text';
this.likes = data.likes || [];
this.likesCount = data.likesCount || 0;
this.commentsCount = data.commentsCount || 0;
this.createdAt = data.createdAt || new Date().toISOString();
this.updatedAt = data.updatedAt || new Date().toISOString();
}
static async create(postData) {
const { user, content, imageUrl, cloudinaryPublicId } = postData;
const errorContext = createErrorContext('Post', 'create', {
user,
content: content?.substring(0, 100) + '...',
hasImage: !!imageUrl
});
return await withErrorHandling(async () => {
// Validate input first before database operations
const post = new Post(postData);
// Get user data for embedding
const userDoc = await couchdbService.findUserById(user);
if (!userDoc) {
throw new NotFoundError('User', user);
}
post._id = `post_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
post.user = {
userId: user,
name: userDoc.name,
profilePicture: userDoc.profilePicture || ""
},
content,
imageUrl,
cloudinaryPublicId,
likes: [],
likesCount: 0,
commentsCount: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
};
const createdPost = await couchdbService.create(post);
const createdPost = await couchdbService.create(post.toJSON());
// Update user's posts array
userDoc.posts.push(createdPost._id);
userDoc.stats.postsCreated = userDoc.posts.length;
await couchdbService.update(user, userDoc);
// Update user's posts array
userDoc.posts.push(createdPost._id);
userDoc.stats.postsCreated = userDoc.posts.length;
await couchdbService.update(user, userDoc);
return createdPost;
return createdPost;
}, errorContext);
}
static async findById(postId) {
return await couchdbService.getById(postId);
const errorContext = createErrorContext('Post', 'findById', { postId });
return await withErrorHandling(async () => {
const doc = await couchdbService.getById(postId);
if (doc && (doc.type === "post" || ['text', 'image', 'achievement'].includes(doc.type))) {
return new Post(doc);
}
return null;
}, errorContext);
}
static async find(query = {}) {
const couchQuery = {
selector: {
type: "post",
...query
}
};
return await couchdbService.find(couchQuery);
const errorContext = createErrorContext('Post', 'find', { query });
return await withErrorHandling(async () => {
const couchQuery = {
selector: {
type: "post",
...query
}
};
return await couchdbService.find(couchQuery);
}, errorContext);
}
static async findAll(options = {}) {
const { skip = 0, limit = 20, sort = { createdAt: -1 } } = options;
const errorContext = createErrorContext('Post', 'findAll', { options });
const query = {
selector: { type: "post" },
sort: Object.keys(sort).map(key => [key, sort[key] === -1 ? "desc" : "asc"]),
skip,
limit
};
return await withErrorHandling(async () => {
const query = {
selector: { type: "post" },
sort: Object.keys(sort).map(key => [key, sort[key] === -1 ? "desc" : "asc"]),
skip,
limit
};
return await couchdbService.find(query);
return await couchdbService.find(query);
}, errorContext);
}
static async countDocuments() {
const query = {
selector: { type: "post" },
fields: ["_id"]
};
const docs = await couchdbService.find(query);
return docs.length;
const errorContext = createErrorContext('Post', 'countDocuments');
return await withErrorHandling(async () => {
const query = {
selector: { type: "post" },
fields: ["_id"]
};
const docs = await couchdbService.find(query);
return docs.length;
}, errorContext);
}
static async updatePost(postId, updateData) {
const post = await couchdbService.getById(postId);
if (!post) {
throw new Error("Post not found");
}
const errorContext = createErrorContext('Post', 'updatePost', { postId, updateData });
return await withErrorHandling(async () => {
const post = await couchdbService.getById(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
const updatedPost = {
...post,
...updateData,
updatedAt: new Date().toISOString()
};
const updatedPost = {
...post,
...updateData,
updatedAt: new Date().toISOString()
};
return await couchdbService.update(postId, updatedPost);
return await couchdbService.update(postId, updatedPost);
}, errorContext);
}
static async deletePost(postId) {
const post = await couchdbService.getById(postId);
if (!post) {
throw new Error("Post not found");
}
// Remove post from user's posts array
if (post.user && post.user.userId) {
const userDoc = await couchdbService.findUserById(post.user.userId);
if (userDoc) {
userDoc.posts = userDoc.posts.filter(id => id !== postId);
userDoc.stats.postsCreated = userDoc.posts.length;
await couchdbService.update(post.user.userId, userDoc);
const errorContext = createErrorContext('Post', 'deletePost', { postId });
return await withErrorHandling(async () => {
const post = await couchdbService.getById(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
}
return await couchdbService.delete(postId);
// Remove post from user's posts array
if (post.user && post.user.userId) {
const userDoc = await couchdbService.findUserById(post.user.userId);
if (userDoc) {
userDoc.posts = userDoc.posts.filter(id => id !== postId);
userDoc.stats.postsCreated = userDoc.posts.length;
await couchdbService.update(post.user.userId, userDoc);
}
}
return await couchdbService.delete(postId);
}, errorContext);
}
static async addLike(postId, userId) {
const post = await couchdbService.getById(postId);
if (!post) {
throw new Error("Post not found");
}
const errorContext = createErrorContext('Post', 'addLike', { postId, userId });
return await withErrorHandling(async () => {
const post = await couchdbService.getById(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
if (!post.likes.includes(userId)) {
post.likes.push(userId);
post.likesCount = post.likes.length;
post.updatedAt = new Date().toISOString();
await couchdbService.update(postId, post);
}
if (!post.likes.includes(userId)) {
post.likes.push(userId);
post.likesCount = post.likes.length;
post.updatedAt = new Date().toISOString();
await couchdbService.update(postId, post);
}
return post;
return post;
}, errorContext);
}
static async removeLike(postId, userId) {
const post = await couchdbService.getById(postId);
if (!post) {
throw new Error("Post not found");
}
const errorContext = createErrorContext('Post', 'removeLike', { postId, userId });
return await withErrorHandling(async () => {
const post = await couchdbService.getById(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
const likeIndex = post.likes.indexOf(userId);
if (likeIndex > -1) {
post.likes.splice(likeIndex, 1);
post.likesCount = post.likes.length;
post.updatedAt = new Date().toISOString();
await couchdbService.update(postId, post);
}
const likeIndex = post.likes.indexOf(userId);
if (likeIndex > -1) {
post.likes.splice(likeIndex, 1);
post.likesCount = post.likes.length;
post.updatedAt = new Date().toISOString();
await couchdbService.update(postId, post);
}
return post;
return post;
}, errorContext);
}
static async incrementCommentsCount(postId) {
const post = await couchdbService.getById(postId);
if (!post) {
throw new Error("Post not found");
}
const errorContext = createErrorContext('Post', 'incrementCommentsCount', { postId });
return await withErrorHandling(async () => {
const post = await couchdbService.getById(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
post.commentsCount = (post.commentsCount || 0) + 1;
post.updatedAt = new Date().toISOString();
return await couchdbService.update(postId, post);
post.commentsCount = (post.commentsCount || 0) + 1;
post.updatedAt = new Date().toISOString();
return await couchdbService.update(postId, post);
}, errorContext);
}
static async decrementCommentsCount(postId) {
const post = await couchdbService.getById(postId);
if (!post) {
throw new Error("Post not found");
}
const errorContext = createErrorContext('Post', 'decrementCommentsCount', { postId });
return await withErrorHandling(async () => {
const post = await couchdbService.getById(postId);
if (!post) {
throw new NotFoundError('Post', postId);
}
post.commentsCount = Math.max(0, (post.commentsCount || 0) - 1);
post.updatedAt = new Date().toISOString();
return await couchdbService.update(postId, post);
post.commentsCount = Math.max(0, (post.commentsCount || 0) - 1);
post.updatedAt = new Date().toISOString();
return await couchdbService.update(postId, post);
}, errorContext);
}
static async findByUserId(userId, options = {}) {
const { skip = 0, limit = 20 } = options;
const errorContext = createErrorContext('Post', 'findByUserId', { userId, options });
const query = {
selector: {
type: "post",
"user.userId": userId
},
sort: [["createdAt", "desc"]],
skip,
limit
};
return await withErrorHandling(async () => {
const query = {
selector: {
type: "post",
"user.userId": userId
},
sort: [["createdAt", "desc"]],
skip,
limit
};
return await couchdbService.find(query);
return await couchdbService.find(query);
}, errorContext);
}
// Convert to CouchDB document format
toJSON() {
return {
_id: this._id,
_rev: this._rev,
type: this.type,
user: this.user,
content: this.content,
imageUrl: this.imageUrl,
cloudinaryPublicId: this.cloudinaryPublicId,
postType: this.postType,
likes: this.likes,
likesCount: this.likesCount,
commentsCount: this.commentsCount,
createdAt: this.createdAt,
updatedAt: this.updatedAt
};
}
// Instance save method
async save() {
const errorContext = createErrorContext('Post', 'save', {
id: this._id,
isNew: !this._id
});
return await withErrorHandling(async () => {
if (!this._id) {
// New document
this._id = `post_${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);
}
// Legacy compatibility methods for mongoose-like interface

View File

@@ -1,108 +1,238 @@
const couchdbService = require("../services/couchdbService");
const {
ValidationError,
NotFoundError,
DatabaseError,
withErrorHandling,
createErrorContext
} = require("../utils/modelErrors");
class Report {
static async create(reportData) {
const doc = {
type: "report",
...reportData,
status: reportData.status || "open",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
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);
}
return await couchdbService.createDocument(doc);
// 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 doc = await couchdbService.getDocument(id);
if (doc && doc.type === "report") {
return doc;
}
return null;
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 selector = {
type: "report",
...filter,
};
const errorContext = createErrorContext('Report', 'find', { filter });
return await withErrorHandling(async () => {
const selector = {
type: "report",
...filter,
};
return await couchdbService.findDocuments(selector);
return await couchdbService.findDocuments(selector);
}, errorContext);
}
static async findWithPagination(options = {}) {
const { page = 1, limit = 10, sort = { createdAt: -1 } } = options;
const selector = { type: "report" };
const errorContext = createErrorContext('Report', 'findWithPagination', { options });
return await withErrorHandling(async () => {
const selector = { type: "report" };
return await couchdbService.findWithPagination(selector, {
page,
limit,
sort,
});
return await couchdbService.findWithPagination(selector, {
page,
limit,
sort,
});
}, errorContext);
}
static async update(id, updateData) {
const doc = await couchdbService.getDocument(id);
if (!doc || doc.type !== "report") {
throw new Error("Report not found");
}
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(),
};
const updatedDoc = {
...doc,
...updateData,
updatedAt: new Date().toISOString(),
};
return await couchdbService.updateDocument(id, updatedDoc);
return await couchdbService.updateDocument(id, updatedDoc);
}, errorContext);
}
static async delete(id) {
const doc = await couchdbService.getDocument(id);
if (!doc || doc.type !== "report") {
throw new Error("Report not found");
}
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);
return await couchdbService.deleteDocument(id, doc._rev);
}, errorContext);
}
static async countDocuments(filter = {}) {
const selector = {
type: "report",
...filter,
};
const errorContext = createErrorContext('Report', 'countDocuments', { filter });
return await withErrorHandling(async () => {
const selector = {
type: "report",
...filter,
};
return await couchdbService.countDocuments(selector);
return await couchdbService.countDocuments(selector);
}, errorContext);
}
static async findByStreet(streetId) {
const selector = {
type: "report",
"street._id": streetId,
};
const errorContext = createErrorContext('Report', 'findByStreet', { streetId });
return await withErrorHandling(async () => {
const selector = {
type: "report",
"street._id": streetId,
};
return await couchdbService.findDocuments(selector);
return await couchdbService.findDocuments(selector);
}, errorContext);
}
static async findByUser(userId) {
const selector = {
type: "report",
"user._id": userId,
};
const errorContext = createErrorContext('Report', 'findByUser', { userId });
return await withErrorHandling(async () => {
const selector = {
type: "report",
"user._id": userId,
};
return await couchdbService.findDocuments(selector);
return await couchdbService.findDocuments(selector);
}, errorContext);
}
static async findByStatus(status) {
const selector = {
type: "report",
status,
};
const errorContext = createErrorContext('Report', 'findByStatus', { status });
return await withErrorHandling(async () => {
const selector = {
type: "report",
status,
};
return await couchdbService.findDocuments(selector);
return await couchdbService.findDocuments(selector);
}, errorContext);
}
static async update(id, updateData) {
return await couchdbService.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);
}
}

View File

@@ -1,17 +1,25 @@
const bcrypt = require("bcryptjs");
const couchdbService = require("../services/couchdbService");
const {
ValidationError,
NotFoundError,
DatabaseError,
DuplicateError,
withErrorHandling,
createErrorContext
} = require("../utils/modelErrors");
class User {
constructor(data) {
// Validate required fields
if (!data.name) {
throw new Error('Name is required');
throw new ValidationError('Name is required', 'name', data.name);
}
if (!data.email) {
throw new Error('Email is required');
throw new ValidationError('Email is required', 'email', data.email);
}
if (!data.password) {
throw new Error('Password is required');
throw new ValidationError('Password is required', 'password', data.password);
}
this._id = data._id || null;
@@ -42,97 +50,136 @@ class User {
// Static methods for MongoDB compatibility
static async findOne(query) {
let user;
if (query.email) {
user = await couchdbService.findUserByEmail(query.email);
} else if (query._id) {
user = await couchdbService.findUserById(query._id);
} else {
// Generic query fallback
const docs = await couchdbService.find({
selector: { type: "user", ...query },
limit: 1
});
user = docs[0] || null;
}
return user ? new User(user) : null;
const errorContext = createErrorContext('User', 'findOne', { query });
return await withErrorHandling(async () => {
let user;
if (query.email) {
user = await couchdbService.findUserByEmail(query.email);
} else if (query._id) {
user = await couchdbService.findUserById(query._id);
} else {
// Generic query fallback
const docs = await couchdbService.find({
selector: { type: "user", ...query },
limit: 1
});
user = docs[0] || null;
}
return user ? new User(user) : null;
}, errorContext);
}
static async findById(id) {
const user = await couchdbService.findUserById(id);
return user ? new User(user) : null;
const errorContext = createErrorContext('User', 'findById', { id });
return await withErrorHandling(async () => {
const user = await couchdbService.findUserById(id);
return user ? new User(user) : null;
}, errorContext);
}
static async findByIdAndUpdate(id, update, options = {}) {
const user = await couchdbService.findUserById(id);
if (!user) return null;
const updatedUser = { ...user, ...update, updatedAt: new Date().toISOString() };
const saved = await couchdbService.update(id, updatedUser);
const errorContext = createErrorContext('User', 'findByIdAndUpdate', { id, update, options });
if (options.new) {
return saved;
}
return user;
return await withErrorHandling(async () => {
const user = await couchdbService.findUserById(id);
if (!user) return null;
const updatedUser = { ...user, ...update, updatedAt: new Date().toISOString() };
const saved = await couchdbService.update(id, updatedUser);
if (options.new) {
return saved;
}
return user;
}, errorContext);
}
static async findByIdAndDelete(id) {
const user = await couchdbService.findUserById(id);
if (!user) return null;
const errorContext = createErrorContext('User', 'findByIdAndDelete', { id });
await couchdbService.delete(id);
return user;
return await withErrorHandling(async () => {
const user = await couchdbService.findUserById(id);
if (!user) return null;
await couchdbService.delete(id);
return user;
}, errorContext);
}
static async find(query = {}) {
const selector = { type: "user", ...query };
return await couchdbService.find({ selector });
const errorContext = createErrorContext('User', 'find', { query });
return await withErrorHandling(async () => {
const selector = { type: "user", ...query };
return await couchdbService.find({ selector });
}, errorContext);
}
static async create(userData) {
const user = new User(userData);
const errorContext = createErrorContext('User', 'create', { userData: { ...userData, password: '[REDACTED]' } });
// Hash password if provided
if (user.password) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
return await withErrorHandling(async () => {
const user = new User(userData);
// Hash password if provided
if (user.password) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
// Generate ID if not provided
if (!user._id) {
user._id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Generate ID if not provided
if (!user._id) {
user._id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
const created = await couchdbService.createDocument(user.toJSON());
return new User(created);
const created = await couchdbService.createDocument(user.toJSON());
return new User(created);
}, errorContext);
}
// Instance methods
async save() {
if (!this._id) {
// New document
this._id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Hash password if not already hashed
if (this.password && !this.password.startsWith('$2')) {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
const errorContext = createErrorContext('User', 'save', {
id: this._id,
email: this.email,
isNew: !this._id
});
return await withErrorHandling(async () => {
if (!this._id) {
// New document
this._id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Hash password if not already hashed
if (this.password && !this.password.startsWith('$2')) {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
}
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;
}
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);
}
async comparePassword(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
const errorContext = createErrorContext('User', 'comparePassword', {
id: this._id,
email: this.email
});
return await withErrorHandling(async () => {
return await bcrypt.compare(candidatePassword, this.password);
}, errorContext);
}
// Helper method to get user without password
@@ -168,11 +215,15 @@ class User {
// Static method for select functionality
static async select(fields) {
const users = await couchdbService.find({
selector: { type: "user" },
fields: fields
});
return users.map(user => new User(user));
const errorContext = createErrorContext('User', 'select', { fields });
return await withErrorHandling(async () => {
const users = await couchdbService.find({
selector: { type: "user" },
fields: fields
});
return users.map(user => new User(user));
}, errorContext);
}
}