feat: complete Event model standardized error handling
- Update Event.js with class-based structure and standardized error handling - Add constructor validation for required fields (title, description, date, location) - Implement withErrorHandling wrapper for all static methods - Add toJSON() and save() instance methods - Fix test infrastructure to use correct mock methods (createDocument vs create) - All 19 Event tests now passing with proper error handling 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -1,29 +1,24 @@
|
|||||||
// Mock CouchDB service for testing
|
|
||||||
const mockCouchdbService = {
|
|
||||||
createDocument: jest.fn(),
|
|
||||||
findDocumentById: jest.fn(),
|
|
||||||
updateDocument: jest.fn(),
|
|
||||||
findByType: jest.fn(),
|
|
||||||
initialize: jest.fn(),
|
|
||||||
getDocument: jest.fn(),
|
|
||||||
findUserById: jest.fn(),
|
|
||||||
update: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock the service module
|
const Event = require('../../models/Event');
|
||||||
jest.mock('../../services/couchdbService', () => mockCouchdbService);
|
|
||||||
|
|
||||||
describe('Event Model', () => {
|
describe('Event Model', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
// Reset all mocks to ensure clean state
|
// Reset all mocks to ensure clean state
|
||||||
mockCouchdbService.createDocument.mockReset();
|
global.mockCouchdbService.createDocument.mockReset();
|
||||||
mockCouchdbService.findDocumentById.mockReset();
|
global.mockCouchdbService.findDocumentById.mockReset();
|
||||||
mockCouchdbService.updateDocument.mockReset();
|
global.mockCouchdbService.updateDocument.mockReset();
|
||||||
mockCouchdbService.findByType.mockReset();
|
global.mockCouchdbService.findByType.mockReset();
|
||||||
mockCouchdbService.create.mockReset();
|
global.mockCouchdbService.createDocument.mockReset();
|
||||||
mockCouchdbService.getById.mockReset();
|
global.mockCouchdbService.getById.mockReset();
|
||||||
mockCouchdbService.find.mockReset();
|
global.mockCouchdbService.find.mockReset();
|
||||||
|
|
||||||
|
// Set up default implementations for tests that don't override them
|
||||||
|
global.mockCouchdbService.createDocument.mockImplementation((doc) => Promise.resolve({
|
||||||
|
_id: `test_${Date.now()}`,
|
||||||
|
_rev: '1-test',
|
||||||
|
...doc
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Schema Validation', () => {
|
describe('Schema Validation', () => {
|
||||||
@@ -47,7 +42,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.create.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -122,7 +117,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -151,7 +146,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -180,7 +175,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -214,7 +209,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -255,7 +250,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -288,7 +283,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -319,7 +314,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -329,12 +324,16 @@ describe('Event Model', () => {
|
|||||||
expect(typeof event.updatedAt).toBe('string');
|
expect(typeof event.updatedAt).toBe('string');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update updatedAt on modification', async () => {
|
it('should update updatedAt on modification', async () => {
|
||||||
const eventData = {
|
const eventData = {
|
||||||
title: 'Update Test Event',
|
title: 'Timestamp Event',
|
||||||
description: 'Testing update timestamp',
|
description: 'Testing timestamps',
|
||||||
date: '2023-12-01T10:00:00.000Z',
|
date: '2023-12-01T10:00:00.000Z',
|
||||||
location: 'Test Location',
|
location: 'Test Location',
|
||||||
|
participants: [],
|
||||||
|
status: 'upcoming',
|
||||||
|
createdAt: '2023-01-01T00:00:00.000Z',
|
||||||
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockEvent = {
|
const mockEvent = {
|
||||||
@@ -348,19 +347,24 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.findDocumentById.mockResolvedValue(mockEvent);
|
global.mockCouchdbService.getById.mockResolvedValue(mockEvent);
|
||||||
mockCouchdbService.updateDocument.mockResolvedValue({
|
global.mockCouchdbService.updateDocument.mockResolvedValue({
|
||||||
...mockEvent,
|
...mockEvent,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
_rev: '2-def',
|
_rev: '2-def'
|
||||||
updatedAt: '2023-01-01T00:00:01.000Z'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const event = await Event.findById('event_123');
|
const event = await Event.findById('event_123');
|
||||||
|
const originalUpdatedAt = event.updatedAt;
|
||||||
|
|
||||||
|
// Wait a bit to ensure different timestamp
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1));
|
||||||
|
|
||||||
event.status = 'completed';
|
event.status = 'completed';
|
||||||
await event.save();
|
await event.save();
|
||||||
|
|
||||||
expect(event.updatedAt).toBe('2023-01-01T00:00:01.000Z');
|
expect(event.updatedAt).not.toBe(originalUpdatedAt);
|
||||||
|
expect(event.updatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -391,7 +395,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
|
||||||
|
|
||||||
const event = await Event.create(eventData);
|
const event = await Event.create(eventData);
|
||||||
|
|
||||||
@@ -416,7 +420,7 @@ describe('Event Model', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00.000Z'
|
updatedAt: '2023-01-01T00:00:00.000Z'
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCouchdbService.findDocumentById.mockResolvedValue(mockEvent);
|
global.mockCouchdbService.getById.mockResolvedValue(mockEvent);
|
||||||
|
|
||||||
const event = await Event.findById('event_123');
|
const event = await Event.findById('event_123');
|
||||||
expect(event).toBeDefined();
|
expect(event).toBeDefined();
|
||||||
@@ -425,7 +429,7 @@ describe('Event Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return null when event not found', async () => {
|
it('should return null when event not found', async () => {
|
||||||
mockCouchdbService.findDocumentById.mockResolvedValue(null);
|
global.mockCouchdbService.getById.mockResolvedValue(null);
|
||||||
|
|
||||||
const event = await Event.findById('nonexistent');
|
const event = await Event.findById('nonexistent');
|
||||||
expect(event).toBeNull();
|
expect(event).toBeNull();
|
||||||
|
|||||||
@@ -1,213 +1,377 @@
|
|||||||
const couchdbService = require("../services/couchdbService");
|
const couchdbService = require("../services/couchdbService");
|
||||||
|
const {
|
||||||
|
ValidationError,
|
||||||
|
NotFoundError,
|
||||||
|
DatabaseError,
|
||||||
|
withErrorHandling,
|
||||||
|
createErrorContext,
|
||||||
|
logModelError
|
||||||
|
} = require("../utils/modelErrors");
|
||||||
|
|
||||||
class Event {
|
class Event {
|
||||||
static async create(eventData) {
|
constructor(data) {
|
||||||
const event = {
|
// Handle both new documents and database documents
|
||||||
_id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
const isNew = !data._id;
|
||||||
type: "event",
|
|
||||||
title: eventData.title,
|
// For new documents, validate required fields
|
||||||
description: eventData.description,
|
if (isNew) {
|
||||||
date: eventData.date,
|
if (!data.title || data.title.trim() === '') {
|
||||||
location: eventData.location,
|
throw new ValidationError('Title is required', 'title', data.title);
|
||||||
participants: [],
|
}
|
||||||
participantsCount: 0,
|
if (!data.description || data.description.trim() === '') {
|
||||||
status: eventData.status || "upcoming",
|
throw new ValidationError('Description is required', 'description', data.description);
|
||||||
createdAt: new Date().toISOString(),
|
}
|
||||||
updatedAt: new Date().toISOString()
|
if (!data.date) {
|
||||||
};
|
throw new ValidationError('Date is required', 'date', data.date);
|
||||||
|
}
|
||||||
|
if (!data.location) {
|
||||||
|
throw new ValidationError('Location is required', 'location', data.location);
|
||||||
|
}
|
||||||
|
|
||||||
return await couchdbService.create(event);
|
// Validate status
|
||||||
|
const validStatuses = ["upcoming", "ongoing", "completed", "cancelled"];
|
||||||
|
if (data.status && !validStatuses.includes(data.status)) {
|
||||||
|
throw new ValidationError('Invalid status', 'status', data.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._id = data._id || null;
|
||||||
|
this._rev = data._rev || null;
|
||||||
|
this.type = data.type || "event";
|
||||||
|
this.title = data.title;
|
||||||
|
this.description = data.description;
|
||||||
|
this.date = data.date;
|
||||||
|
this.location = data.location;
|
||||||
|
this.organizer = data.organizer || null;
|
||||||
|
this.participants = data.participants || [];
|
||||||
|
this.participantsCount = data.participantsCount || 0;
|
||||||
|
this.status = data.status || "upcoming";
|
||||||
|
this.createdAt = data.createdAt || new Date().toISOString();
|
||||||
|
this.updatedAt = data.updatedAt || new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(eventData) {
|
||||||
|
const errorContext = createErrorContext('Event', 'create', {
|
||||||
|
title: eventData?.title,
|
||||||
|
date: eventData?.date,
|
||||||
|
location: eventData?.location
|
||||||
|
});
|
||||||
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
const event = new Event(eventData);
|
||||||
|
event._id = `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
||||||
|
const createdEvent = await couchdbService.createDocument(event.toJSON());
|
||||||
|
return new Event(createdEvent);
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findById(eventId) {
|
static async findById(eventId) {
|
||||||
return await couchdbService.getById(eventId);
|
const errorContext = createErrorContext('Event', 'findById', { eventId });
|
||||||
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
const doc = await couchdbService.getById(eventId);
|
||||||
|
if (doc && doc.type === "event") {
|
||||||
|
return new Event(doc);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async find(query = {}, options = {}) {
|
static async find(query = {}, options = {}) {
|
||||||
const defaultQuery = {
|
const errorContext = createErrorContext('Event', 'find', { query, options });
|
||||||
type: "event",
|
|
||||||
...query
|
return await withErrorHandling(async () => {
|
||||||
};
|
const defaultQuery = {
|
||||||
|
type: "event",
|
||||||
|
...query
|
||||||
|
};
|
||||||
|
|
||||||
return await couchdbService.find({
|
return await couchdbService.find({
|
||||||
selector: defaultQuery,
|
selector: defaultQuery,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findOne(query) {
|
static async findOne(query) {
|
||||||
const events = await this.find(query, { limit: 1 });
|
const errorContext = createErrorContext('Event', 'findOne', { query });
|
||||||
return events[0] || null;
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
const events = await this.find(query, { limit: 1 });
|
||||||
|
return events[0] || null;
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(eventId, updateData) {
|
static async update(eventId, updateData) {
|
||||||
const event = await this.findById(eventId);
|
const errorContext = createErrorContext('Event', 'update', { eventId, updateData });
|
||||||
if (!event) {
|
|
||||||
throw new Error("Event not found");
|
return await withErrorHandling(async () => {
|
||||||
}
|
const event = await this.findById(eventId);
|
||||||
|
if (!event) {
|
||||||
|
throw new NotFoundError('Event', eventId);
|
||||||
|
}
|
||||||
|
|
||||||
const updatedEvent = {
|
const updatedEvent = {
|
||||||
...event,
|
...event.toJSON(),
|
||||||
...updateData,
|
...updateData,
|
||||||
updatedAt: new Date().toISOString()
|
updatedAt: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
return await couchdbService.update(eventId, updatedEvent);
|
const result = await couchdbService.update(eventId, updatedEvent);
|
||||||
|
return new Event(result);
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async delete(eventId) {
|
static async delete(eventId) {
|
||||||
return await couchdbService.delete(eventId);
|
const errorContext = createErrorContext('Event', 'delete', { eventId });
|
||||||
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
const event = await this.findById(eventId);
|
||||||
|
if (!event) {
|
||||||
|
throw new NotFoundError('Event', eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await couchdbService.delete(eventId);
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addParticipant(eventId, userId, userName, userProfilePicture) {
|
static async addParticipant(eventId, userId, userName, userProfilePicture) {
|
||||||
const event = await this.findById(eventId);
|
const errorContext = createErrorContext('Event', 'addParticipant', { eventId, userId });
|
||||||
if (!event) {
|
|
||||||
throw new Error("Event not found");
|
return await withErrorHandling(async () => {
|
||||||
}
|
const event = await this.findById(eventId);
|
||||||
|
if (!event) {
|
||||||
|
throw new NotFoundError('Event', eventId);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user is already a participant
|
// Check if user is already a participant
|
||||||
const existingParticipant = event.participants.find(p => p.userId === userId);
|
const existingParticipant = event.participants.find(p => p.userId === userId);
|
||||||
if (existingParticipant) {
|
if (existingParticipant) {
|
||||||
throw new Error("User already participating in this event");
|
throw new ValidationError('User already participating in this event', 'userId', userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add participant with embedded user data
|
// Add participant with embedded user data
|
||||||
const newParticipant = {
|
const newParticipant = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
name: userName,
|
name: userName,
|
||||||
profilePicture: userProfilePicture || "",
|
profilePicture: userProfilePicture || "",
|
||||||
joinedAt: new Date().toISOString()
|
joinedAt: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
event.participants.push(newParticipant);
|
event.participants.push(newParticipant);
|
||||||
event.participantsCount = event.participants.length;
|
event.participantsCount = event.participants.length;
|
||||||
event.updatedAt = new Date().toISOString();
|
event.updatedAt = new Date().toISOString();
|
||||||
|
|
||||||
return await couchdbService.update(eventId, event);
|
const result = await couchdbService.update(eventId, event.toJSON());
|
||||||
|
return new Event(result);
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeParticipant(eventId, userId) {
|
static async removeParticipant(eventId, userId) {
|
||||||
const event = await this.findById(eventId);
|
const errorContext = createErrorContext('Event', 'removeParticipant', { eventId, userId });
|
||||||
if (!event) {
|
|
||||||
throw new Error("Event not found");
|
return await withErrorHandling(async () => {
|
||||||
}
|
const event = await this.findById(eventId);
|
||||||
|
if (!event) {
|
||||||
|
throw new NotFoundError('Event', eventId);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove participant
|
// Remove participant
|
||||||
event.participants = event.participants.filter(p => p.userId !== userId);
|
event.participants = event.participants.filter(p => p.userId !== userId);
|
||||||
event.participantsCount = event.participants.length;
|
event.participantsCount = event.participants.length;
|
||||||
event.updatedAt = new Date().toISOString();
|
event.updatedAt = new Date().toISOString();
|
||||||
|
|
||||||
return await couchdbService.update(eventId, event);
|
const result = await couchdbService.update(eventId, event.toJSON());
|
||||||
|
return new Event(result);
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateStatus(eventId, newStatus) {
|
static async updateStatus(eventId, newStatus) {
|
||||||
const validStatuses = ["upcoming", "ongoing", "completed", "cancelled"];
|
const errorContext = createErrorContext('Event', 'updateStatus', { eventId, newStatus });
|
||||||
if (!validStatuses.includes(newStatus)) {
|
|
||||||
throw new Error("Invalid status");
|
return await withErrorHandling(async () => {
|
||||||
}
|
const validStatuses = ["upcoming", "ongoing", "completed", "cancelled"];
|
||||||
|
if (!validStatuses.includes(newStatus)) {
|
||||||
|
throw new ValidationError('Invalid status', 'status', newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
return await this.update(eventId, { status: newStatus });
|
return await this.update(eventId, { status: newStatus });
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findByStatus(status) {
|
static async findByStatus(status) {
|
||||||
return await this.find({ status });
|
const errorContext = createErrorContext('Event', 'findByStatus', { status });
|
||||||
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
return await this.find({ status });
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findByDateRange(startDate, endDate) {
|
static async findByDateRange(startDate, endDate) {
|
||||||
return await couchdbService.find({
|
const errorContext = createErrorContext('Event', 'findByDateRange', { startDate, endDate });
|
||||||
selector: {
|
|
||||||
type: "event",
|
return await withErrorHandling(async () => {
|
||||||
date: {
|
return await couchdbService.find({
|
||||||
$gte: startDate.toISOString(),
|
selector: {
|
||||||
$lte: endDate.toISOString()
|
type: "event",
|
||||||
}
|
date: {
|
||||||
},
|
$gte: startDate.toISOString(),
|
||||||
sort: [{ date: "asc" }]
|
$lte: endDate.toISOString()
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
sort: [{ date: "asc" }]
|
||||||
|
});
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findByParticipant(userId) {
|
static async findByParticipant(userId) {
|
||||||
return await couchdbService.view("events", "by-participant", {
|
const errorContext = createErrorContext('Event', 'findByParticipant', { userId });
|
||||||
key: userId,
|
|
||||||
include_docs: true
|
return await withErrorHandling(async () => {
|
||||||
});
|
return await couchdbService.view("events", "by-participant", {
|
||||||
|
key: userId,
|
||||||
|
include_docs: true
|
||||||
|
});
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUpcomingEvents(limit = 10) {
|
static async getUpcomingEvents(limit = 10) {
|
||||||
const now = new Date().toISOString();
|
const errorContext = createErrorContext('Event', 'getUpcomingEvents', { limit });
|
||||||
return await couchdbService.find({
|
|
||||||
selector: {
|
return await withErrorHandling(async () => {
|
||||||
type: "event",
|
const now = new Date().toISOString();
|
||||||
status: "upcoming",
|
return await couchdbService.find({
|
||||||
date: { $gte: now }
|
selector: {
|
||||||
},
|
type: "event",
|
||||||
sort: [{ date: "asc" }],
|
status: "upcoming",
|
||||||
limit
|
date: { $gte: now }
|
||||||
});
|
},
|
||||||
|
sort: [{ date: "asc" }],
|
||||||
|
limit
|
||||||
|
});
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAllPaginated(page = 1, limit = 10) {
|
static async getAllPaginated(page = 1, limit = 10) {
|
||||||
const skip = (page - 1) * limit;
|
const errorContext = createErrorContext('Event', 'getAllPaginated', { page, limit });
|
||||||
|
|
||||||
const events = await couchdbService.find({
|
return await withErrorHandling(async () => {
|
||||||
selector: { type: "event" },
|
const skip = (page - 1) * limit;
|
||||||
sort: [{ date: "desc" }],
|
|
||||||
skip,
|
const events = await couchdbService.find({
|
||||||
limit
|
selector: { type: "event" },
|
||||||
});
|
sort: [{ date: "desc" }],
|
||||||
|
skip,
|
||||||
|
limit
|
||||||
|
});
|
||||||
|
|
||||||
// Get total count
|
// Get total count
|
||||||
const totalCount = await couchdbService.find({
|
const totalCount = await couchdbService.find({
|
||||||
selector: { type: "event" },
|
selector: { type: "event" },
|
||||||
fields: ["_id"]
|
fields: ["_id"]
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
events,
|
events,
|
||||||
pagination: {
|
pagination: {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
totalCount: totalCount.length,
|
totalCount: totalCount.length,
|
||||||
totalPages: Math.ceil(totalCount.length / limit)
|
totalPages: Math.ceil(totalCount.length / limit)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getEventsByUser(userId) {
|
static async getEventsByUser(userId) {
|
||||||
return await this.find({
|
const errorContext = createErrorContext('Event', 'getEventsByUser', { userId });
|
||||||
"participants": { $elemMatch: { userId: userId } }
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
return await this.find({
|
||||||
|
"participants": { $elemMatch: { userId: userId } }
|
||||||
|
});
|
||||||
|
}, errorContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to CouchDB document format
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
_id: this._id,
|
||||||
|
_rev: this._rev,
|
||||||
|
type: this.type,
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
date: this.date,
|
||||||
|
location: this.location,
|
||||||
|
organizer: this.organizer,
|
||||||
|
participants: this.participants,
|
||||||
|
participantsCount: this.participantsCount,
|
||||||
|
status: this.status,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
updatedAt: this.updatedAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance save method
|
||||||
|
async save() {
|
||||||
|
const errorContext = createErrorContext('Event', 'save', {
|
||||||
|
id: this._id,
|
||||||
|
isNew: !this._id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return await withErrorHandling(async () => {
|
||||||
|
if (!this._id) {
|
||||||
|
// New document
|
||||||
|
this._id = `event_${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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration helper
|
// Migration helper
|
||||||
static async migrateFromMongo(mongoEvent) {
|
static async migrateFromMongo(mongoEvent) {
|
||||||
const eventData = {
|
const errorContext = createErrorContext('Event', 'migrateFromMongo', {
|
||||||
title: mongoEvent.title,
|
mongoEventId: mongoEvent._id
|
||||||
description: mongoEvent.description,
|
});
|
||||||
date: mongoEvent.date,
|
|
||||||
location: mongoEvent.location,
|
return await withErrorHandling(async () => {
|
||||||
status: mongoEvent.status || "upcoming"
|
const eventData = {
|
||||||
};
|
title: mongoEvent.title,
|
||||||
|
description: mongoEvent.description,
|
||||||
|
date: mongoEvent.date,
|
||||||
|
location: mongoEvent.location,
|
||||||
|
status: mongoEvent.status || "upcoming"
|
||||||
|
};
|
||||||
|
|
||||||
// Create event without participants first
|
// Create event without participants first
|
||||||
const event = await this.create(eventData);
|
const event = await this.create(eventData);
|
||||||
|
|
||||||
// If there are participants, add them with embedded user data
|
// If there are participants, add them with embedded user data
|
||||||
if (mongoEvent.participants && mongoEvent.participants.length > 0) {
|
if (mongoEvent.participants && mongoEvent.participants.length > 0) {
|
||||||
for (const participantId of mongoEvent.participants) {
|
for (const participantId of mongoEvent.participants) {
|
||||||
try {
|
try {
|
||||||
// Get user data to embed
|
// Get user data to embed
|
||||||
const user = await couchdbService.findUserById(participantId.toString());
|
const user = await couchdbService.findUserById(participantId.toString());
|
||||||
if (user) {
|
if (user) {
|
||||||
await this.addParticipant(event._id, participantId.toString(), user.name, user.profilePicture);
|
await this.addParticipant(event._id, participantId.toString(), user.name, user.profilePicture);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error migrating participant ${participantId}:`, error.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error migrating participant ${participantId}:`, error.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
|
}, errorContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user