feat: complete MongoDB to CouchDB migration
- Migrate Report model to CouchDB with embedded street/user data - Migrate UserBadge model to CouchDB with badge population - Update all remaining routes (reports, users, badges, payments) to use CouchDB - Add CouchDB health check and graceful shutdown to server.js - Add missing methods to couchdbService (checkConnection, findWithPagination, etc.) - Update Kubernetes deployment manifests for CouchDB support - Add comprehensive CouchDB setup documentation All core functionality now uses CouchDB as primary database while maintaining MongoDB for backward compatibility during transition period. 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -1,41 +1,109 @@
|
||||
const mongoose = require("mongoose");
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
|
||||
const ReportSchema = new mongoose.Schema(
|
||||
{
|
||||
street: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Street",
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
issue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
imageUrl: {
|
||||
type: String,
|
||||
},
|
||||
cloudinaryPublicId: {
|
||||
type: String,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ["open", "resolved"],
|
||||
default: "open",
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
class Report {
|
||||
static async create(reportData) {
|
||||
const doc = {
|
||||
type: "report",
|
||||
...reportData,
|
||||
status: reportData.status || "open",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Indexes for performance
|
||||
ReportSchema.index({ street: 1, status: 1 });
|
||||
ReportSchema.index({ user: 1 });
|
||||
ReportSchema.index({ createdAt: -1 });
|
||||
return await couchdbService.createDocument(doc);
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("Report", ReportSchema);
|
||||
static async findById(id) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (doc && doc.type === "report") {
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static async find(filter = {}) {
|
||||
const selector = {
|
||||
type: "report",
|
||||
...filter,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
}
|
||||
|
||||
static async findWithPagination(options = {}) {
|
||||
const { page = 1, limit = 10, sort = { createdAt: -1 } } = options;
|
||||
const selector = { type: "report" };
|
||||
|
||||
return await couchdbService.findWithPagination(selector, {
|
||||
page,
|
||||
limit,
|
||||
sort,
|
||||
});
|
||||
}
|
||||
|
||||
static async update(id, updateData) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "report") {
|
||||
throw new Error("Report not found");
|
||||
}
|
||||
|
||||
const updatedDoc = {
|
||||
...doc,
|
||||
...updateData,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return await couchdbService.updateDocument(id, updatedDoc);
|
||||
}
|
||||
|
||||
static async delete(id) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "report") {
|
||||
throw new Error("Report not found");
|
||||
}
|
||||
|
||||
return await couchdbService.deleteDocument(id, doc._rev);
|
||||
}
|
||||
|
||||
static async countDocuments(filter = {}) {
|
||||
const selector = {
|
||||
type: "report",
|
||||
...filter,
|
||||
};
|
||||
|
||||
return await couchdbService.countDocuments(selector);
|
||||
}
|
||||
|
||||
static async findByStreet(streetId) {
|
||||
const selector = {
|
||||
type: "report",
|
||||
"street._id": streetId,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
}
|
||||
|
||||
static async findByUser(userId) {
|
||||
const selector = {
|
||||
type: "report",
|
||||
"user._id": userId,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
}
|
||||
|
||||
static async findByStatus(status) {
|
||||
const selector = {
|
||||
type: "report",
|
||||
status,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
}
|
||||
|
||||
static async update(id, updateData) {
|
||||
return await couchdbService.update(id, updateData);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Report;
|
||||
|
||||
@@ -1,37 +1,118 @@
|
||||
const mongoose = require("mongoose");
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
|
||||
const UserBadgeSchema = new mongoose.Schema(
|
||||
{
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
badge: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Badge",
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
earnedAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
class UserBadge {
|
||||
static async create(userBadgeData) {
|
||||
const doc = {
|
||||
type: "user_badge",
|
||||
...userBadgeData,
|
||||
earnedAt: userBadgeData.earnedAt || new Date().toISOString(),
|
||||
progress: userBadgeData.progress || 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Compound unique index to prevent duplicate badge awards
|
||||
UserBadgeSchema.index({ user: 1, badge: 1 }, { unique: true });
|
||||
return await couchdbService.createDocument(doc);
|
||||
}
|
||||
|
||||
// Index for user badge queries
|
||||
UserBadgeSchema.index({ user: 1, earnedAt: -1 });
|
||||
static async findById(id) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (doc && doc.type === "user_badge") {
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("UserBadge", UserBadgeSchema);
|
||||
static async find(filter = {}) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
...filter,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
}
|
||||
|
||||
static async findByUser(userId) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
userId: userId,
|
||||
};
|
||||
|
||||
const userBadges = await couchdbService.findDocuments(selector);
|
||||
|
||||
// Populate badge data for each user badge
|
||||
const populatedBadges = await Promise.all(
|
||||
userBadges.map(async (userBadge) => {
|
||||
if (userBadge.badgeId) {
|
||||
const badge = await couchdbService.getDocument(userBadge.badgeId);
|
||||
return {
|
||||
...userBadge,
|
||||
badge: badge,
|
||||
};
|
||||
}
|
||||
return userBadge;
|
||||
})
|
||||
);
|
||||
|
||||
return populatedBadges;
|
||||
}
|
||||
|
||||
static async findByBadge(badgeId) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
badgeId: badgeId,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
}
|
||||
|
||||
static async update(id, updateData) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "user_badge") {
|
||||
throw new Error("UserBadge not found");
|
||||
}
|
||||
|
||||
const updatedDoc = {
|
||||
...doc,
|
||||
...updateData,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return await couchdbService.updateDocument(id, updatedDoc);
|
||||
}
|
||||
|
||||
static async delete(id) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "user_badge") {
|
||||
throw new Error("UserBadge not found");
|
||||
}
|
||||
|
||||
return await couchdbService.deleteDocument(id, doc._rev);
|
||||
}
|
||||
|
||||
static async findByUserAndBadge(userId, badgeId) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
userId: userId,
|
||||
badgeId: badgeId,
|
||||
};
|
||||
|
||||
const results = await couchdbService.findDocuments(selector);
|
||||
return results[0] || null;
|
||||
}
|
||||
|
||||
static async updateProgress(userId, badgeId, progress) {
|
||||
const userBadge = await this.findByUserAndBadge(userId, badgeId);
|
||||
|
||||
if (userBadge) {
|
||||
return await this.update(userBadge._id, { progress });
|
||||
} else {
|
||||
return await this.create({
|
||||
userId,
|
||||
badgeId,
|
||||
progress,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserBadge;
|
||||
|
||||
Reference in New Issue
Block a user