Files
adopt-a-street/backend/models/Event.js
William Valentin 9ac21fca72 feat: migrate Event and Reward models from MongoDB to CouchDB
- Replace Event model with CouchDB version using couchdbService
- Replace Reward model with CouchDB version using couchdbService
- Update event and reward routes to use new model interfaces
- Handle participant management with embedded user data
- Maintain status transitions for events (upcoming, ongoing, completed, cancelled)
- Preserve catalog functionality and premium vs regular rewards
- Update validators to accept CouchDB document IDs
- Add rewards design document to couchdbService
- Update test helpers for new model structure
- Initialize CouchDB alongside MongoDB in server.js for backward compatibility
- Fix linting issues in migrated routes

🤖 Generated with [AI Assistant]

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
2025-11-01 13:26:00 -07:00

214 lines
5.5 KiB
JavaScript

const couchdbService = require("../services/couchdbService");
class Event {
static async create(eventData) {
const event = {
_id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
type: "event",
title: eventData.title,
description: eventData.description,
date: eventData.date,
location: eventData.location,
participants: [],
participantsCount: 0,
status: eventData.status || "upcoming",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
return await couchdbService.create(event);
}
static async findById(eventId) {
return await couchdbService.getById(eventId);
}
static async find(query = {}, options = {}) {
const defaultQuery = {
type: "event",
...query
};
return await couchdbService.find({
selector: defaultQuery,
...options
});
}
static async findOne(query) {
const events = await this.find(query, { limit: 1 });
return events[0] || null;
}
static async update(eventId, updateData) {
const event = await this.findById(eventId);
if (!event) {
throw new Error("Event not found");
}
const updatedEvent = {
...event,
...updateData,
updatedAt: new Date().toISOString()
};
return await couchdbService.update(eventId, updatedEvent);
}
static async delete(eventId) {
return await couchdbService.delete(eventId);
}
static async addParticipant(eventId, userId, userName, userProfilePicture) {
const event = await this.findById(eventId);
if (!event) {
throw new Error("Event not found");
}
// Check if user is already a participant
const existingParticipant = event.participants.find(p => p.userId === userId);
if (existingParticipant) {
throw new Error("User already participating in this event");
}
// Add participant with embedded user data
const newParticipant = {
userId: userId,
name: userName,
profilePicture: userProfilePicture || "",
joinedAt: new Date().toISOString()
};
event.participants.push(newParticipant);
event.participantsCount = event.participants.length;
event.updatedAt = new Date().toISOString();
return await couchdbService.update(eventId, event);
}
static async removeParticipant(eventId, userId) {
const event = await this.findById(eventId);
if (!event) {
throw new Error("Event not found");
}
// Remove participant
event.participants = event.participants.filter(p => p.userId !== userId);
event.participantsCount = event.participants.length;
event.updatedAt = new Date().toISOString();
return await couchdbService.update(eventId, event);
}
static async updateStatus(eventId, newStatus) {
const validStatuses = ["upcoming", "ongoing", "completed", "cancelled"];
if (!validStatuses.includes(newStatus)) {
throw new Error("Invalid status");
}
return await this.update(eventId, { status: newStatus });
}
static async findByStatus(status) {
return await this.find({ status });
}
static async findByDateRange(startDate, endDate) {
return await couchdbService.find({
selector: {
type: "event",
date: {
$gte: startDate.toISOString(),
$lte: endDate.toISOString()
}
},
sort: [{ date: "asc" }]
});
}
static async findByParticipant(userId) {
return await couchdbService.view("events", "by-participant", {
key: userId,
include_docs: true
});
}
static async getUpcomingEvents(limit = 10) {
const now = new Date().toISOString();
return await couchdbService.find({
selector: {
type: "event",
status: "upcoming",
date: { $gte: now }
},
sort: [{ date: "asc" }],
limit
});
}
static async getAllPaginated(page = 1, limit = 10) {
const skip = (page - 1) * limit;
const events = await couchdbService.find({
selector: { type: "event" },
sort: [{ date: "desc" }],
skip,
limit
});
// Get total count
const totalCount = await couchdbService.find({
selector: { type: "event" },
fields: ["_id"]
});
return {
events,
pagination: {
page,
limit,
totalCount: totalCount.length,
totalPages: Math.ceil(totalCount.length / limit)
}
};
}
static async getEventsByUser(userId) {
return await this.find({
"participants": { $elemMatch: { userId: userId } }
});
}
// Migration helper
static async migrateFromMongo(mongoEvent) {
const eventData = {
title: mongoEvent.title,
description: mongoEvent.description,
date: mongoEvent.date,
location: mongoEvent.location,
status: mongoEvent.status || "upcoming"
};
// Create event without participants first
const event = await this.create(eventData);
// If there are participants, add them with embedded user data
if (mongoEvent.participants && mongoEvent.participants.length > 0) {
for (const participantId of mongoEvent.participants) {
try {
// Get user data to embed
const user = await couchdbService.findUserById(participantId.toString());
if (user) {
await this.addParticipant(event._id, participantId.toString(), user.name, user.profilePicture);
}
} catch (error) {
console.error(`Error migrating participant ${participantId}:`, error.message);
}
}
}
return event;
}
}
module.exports = Event;