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:
@@ -14,7 +14,12 @@ const router = express.Router();
|
||||
router.get(
|
||||
"/",
|
||||
asyncHandler(async (req, res) => {
|
||||
const badges = await Badge.find().sort({ order: 1, rarity: 1 });
|
||||
const badges = await Badge.find({ type: "badge" });
|
||||
// Sort by order and rarity in JavaScript since CouchDB doesn't support complex sorting
|
||||
badges.sort((a, b) => {
|
||||
if (a.order !== b.order) return a.order - b.order;
|
||||
return a.rarity.localeCompare(b.rarity);
|
||||
});
|
||||
res.json(badges);
|
||||
})
|
||||
);
|
||||
@@ -33,7 +38,7 @@ router.get(
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/users/:userId/badges
|
||||
* GET /api/badges/users/:userId
|
||||
* Get badges earned by a specific user
|
||||
*/
|
||||
router.get(
|
||||
@@ -41,9 +46,10 @@ router.get(
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
|
||||
const userBadges = await UserBadge.find({ user: userId })
|
||||
.populate("badge")
|
||||
.sort({ earnedAt: -1 });
|
||||
const userBadges = await UserBadge.findByUser(userId);
|
||||
|
||||
// Sort by earnedAt in JavaScript
|
||||
userBadges.sort((a, b) => new Date(b.earnedAt) - new Date(a.earnedAt));
|
||||
|
||||
res.json(
|
||||
userBadges.map((ub) => ({
|
||||
|
||||
@@ -14,8 +14,7 @@ router.post("/subscribe", auth, async (req, res) => {
|
||||
return res.status(404).json({ msg: "User not found" });
|
||||
}
|
||||
|
||||
user.isPremium = true;
|
||||
await user.save();
|
||||
await User.update(req.user.id, { isPremium: true });
|
||||
|
||||
res.json({ msg: "Subscription successful" });
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const express = require("express");
|
||||
const Report = require("../models/Report");
|
||||
const User = require("../models/User");
|
||||
const Street = require("../models/Street");
|
||||
const auth = require("../middleware/auth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
@@ -15,23 +17,23 @@ const router = express.Router();
|
||||
router.get(
|
||||
"/",
|
||||
asyncHandler(async (req, res) => {
|
||||
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
|
||||
|
||||
// Parse pagination params
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 10, 100);
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const reports = await Report.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.populate("street", ["name"])
|
||||
.populate("user", ["name", "profilePicture"]);
|
||||
const result = await Report.findWithPagination({
|
||||
page,
|
||||
limit,
|
||||
sort: { createdAt: -1 },
|
||||
});
|
||||
|
||||
const totalCount = await Report.countDocuments();
|
||||
|
||||
res.json(buildPaginatedResponse(reports, totalCount, page, limit));
|
||||
res.json({
|
||||
reports: result.docs,
|
||||
totalCount: result.totalDocs,
|
||||
currentPage: result.page,
|
||||
totalPages: result.totalPages,
|
||||
hasNext: result.hasNextPage,
|
||||
hasPrev: result.hasPrevPage,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -43,11 +45,29 @@ router.post(
|
||||
handleUploadError,
|
||||
createReportValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { street, issue } = req.body;
|
||||
const { street: streetId, issue } = req.body;
|
||||
|
||||
// Get street and user data for embedding
|
||||
const street = await Street.findById(streetId);
|
||||
if (!street) {
|
||||
return res.status(404).json({ msg: "Street not found" });
|
||||
}
|
||||
|
||||
const user = await User.findById(req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: "User not found" });
|
||||
}
|
||||
|
||||
const reportData = {
|
||||
street,
|
||||
user: req.user.id,
|
||||
street: {
|
||||
_id: street._id,
|
||||
name: street.name,
|
||||
},
|
||||
user: {
|
||||
_id: user._id,
|
||||
name: user.name,
|
||||
profilePicture: user.profilePicture,
|
||||
},
|
||||
issue,
|
||||
};
|
||||
|
||||
@@ -61,15 +81,7 @@ router.post(
|
||||
reportData.cloudinaryPublicId = result.publicId;
|
||||
}
|
||||
|
||||
const newReport = new Report(reportData);
|
||||
const report = await newReport.save();
|
||||
|
||||
// Populate user and street data
|
||||
await report.populate([
|
||||
{ path: "user", select: "name profilePicture" },
|
||||
{ path: "street", select: "name" },
|
||||
]);
|
||||
|
||||
const report = await Report.create(reportData);
|
||||
res.json(report);
|
||||
}),
|
||||
);
|
||||
@@ -88,7 +100,7 @@ router.post(
|
||||
}
|
||||
|
||||
// Verify user owns the report
|
||||
if (report.user.toString() !== req.user.id) {
|
||||
if (report.user._id !== req.user.id) {
|
||||
return res.status(403).json({ msg: "Not authorized" });
|
||||
}
|
||||
|
||||
@@ -107,11 +119,12 @@ router.post(
|
||||
"adopt-a-street/reports",
|
||||
);
|
||||
|
||||
report.imageUrl = result.url;
|
||||
report.cloudinaryPublicId = result.publicId;
|
||||
await report.save();
|
||||
const updatedReport = await Report.update(req.params.id, {
|
||||
imageUrl: result.url,
|
||||
cloudinaryPublicId: result.publicId,
|
||||
});
|
||||
|
||||
res.json(report);
|
||||
res.json(updatedReport);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -126,11 +139,11 @@ router.put(
|
||||
return res.status(404).json({ msg: "Report not found" });
|
||||
}
|
||||
|
||||
report.status = "resolved";
|
||||
const updatedReport = await Report.update(req.params.id, {
|
||||
status: "resolved",
|
||||
});
|
||||
|
||||
await report.save();
|
||||
|
||||
res.json(report);
|
||||
res.json(updatedReport);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require("express");
|
||||
const User = require("../models/User");
|
||||
const Street = require("../models/Street");
|
||||
const auth = require("../middleware/auth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const { userIdValidation } = require("../middleware/validators/userValidator");
|
||||
@@ -14,11 +15,34 @@ router.get(
|
||||
auth,
|
||||
userIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const user = await User.findById(req.params.id).populate("adoptedStreets");
|
||||
const user = await User.findById(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: "User not found" });
|
||||
}
|
||||
res.json(user);
|
||||
|
||||
// Get adopted streets data
|
||||
let adoptedStreets = [];
|
||||
if (user.adoptedStreets && user.adoptedStreets.length > 0) {
|
||||
adoptedStreets = await Promise.all(
|
||||
user.adoptedStreets.map(async (streetId) => {
|
||||
const street = await Street.findById(streetId);
|
||||
return street ? {
|
||||
_id: street._id,
|
||||
name: street.name,
|
||||
location: street.location,
|
||||
status: street.status,
|
||||
} : null;
|
||||
})
|
||||
);
|
||||
adoptedStreets = adoptedStreets.filter(Boolean);
|
||||
}
|
||||
|
||||
const userWithStreets = {
|
||||
...user,
|
||||
adoptedStreets,
|
||||
};
|
||||
|
||||
res.json(userWithStreets);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -50,13 +74,14 @@ router.post(
|
||||
);
|
||||
|
||||
// Update user with new profile picture
|
||||
user.profilePicture = result.url;
|
||||
user.cloudinaryPublicId = result.publicId;
|
||||
await user.save();
|
||||
const updatedUser = await User.update(req.user.id, {
|
||||
profilePicture: result.url,
|
||||
cloudinaryPublicId: result.publicId,
|
||||
});
|
||||
|
||||
res.json({
|
||||
msg: "Profile picture updated successfully",
|
||||
profilePicture: user.profilePicture,
|
||||
profilePicture: updatedUser.profilePicture,
|
||||
});
|
||||
}),
|
||||
);
|
||||
@@ -79,9 +104,10 @@ router.delete(
|
||||
await deleteImage(user.cloudinaryPublicId);
|
||||
|
||||
// Remove from user
|
||||
user.profilePicture = undefined;
|
||||
user.cloudinaryPublicId = undefined;
|
||||
await user.save();
|
||||
await User.update(req.user.id, {
|
||||
profilePicture: undefined,
|
||||
cloudinaryPublicId: undefined,
|
||||
});
|
||||
|
||||
res.json({ msg: "Profile picture deleted successfully" });
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user