feat: Migrate Street and Task models from MongoDB to CouchDB
- Replace Street model with CouchDB-based implementation - Replace Task model with CouchDB-based implementation - Update routes to use new model interfaces - Handle geospatial queries with CouchDB design documents - Maintain adoption functionality and middleware - Use denormalized document structure with embedded data - Update test files to work with new models - Ensure API compatibility while using CouchDB underneath 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -16,8 +16,11 @@ router.get(
|
||||
"/",
|
||||
auth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const user = await User.findById(req.user.id).select("-password");
|
||||
res.json(user);
|
||||
const user = await User.findById(req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: "User not found" });
|
||||
}
|
||||
res.json(user.toSafeObject());
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -33,20 +36,15 @@ router.post(
|
||||
return res.status(400).json({ success: false, msg: "User already exists" });
|
||||
}
|
||||
|
||||
user = new User({
|
||||
user = await User.create({
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password = await bcrypt.hash(password, salt);
|
||||
|
||||
await user.save();
|
||||
|
||||
const payload = {
|
||||
user: {
|
||||
id: user.id,
|
||||
id: user._id,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -78,14 +76,14 @@ router.post(
|
||||
return res.status(400).json({ success: false, msg: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
const isMatch = await user.comparePassword(password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ success: false, msg: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const payload = {
|
||||
user: {
|
||||
id: user.id,
|
||||
id: user._id,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
const express = require("express");
|
||||
const mongoose = require("mongoose");
|
||||
const Street = require("../models/Street");
|
||||
const User = require("../models/User");
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
const auth = require("../middleware/auth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
createStreetValidation,
|
||||
streetIdValidation,
|
||||
} = require("../middleware/validators/streetValidator");
|
||||
const {
|
||||
awardStreetAdoptionPoints,
|
||||
checkAndAwardBadges,
|
||||
} = require("../services/gamificationService");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -19,7 +15,7 @@ const router = express.Router();
|
||||
router.get(
|
||||
"/",
|
||||
asyncHandler(async (req, res) => {
|
||||
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
|
||||
const { buildPaginatedResponse } = require("../middleware/pagination");
|
||||
|
||||
// Parse pagination params
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
@@ -27,10 +23,16 @@ router.get(
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const streets = await Street.find()
|
||||
.sort({ name: 1 })
|
||||
.sort([{ name: "asc" }])
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.populate("adoptedBy", ["name", "profilePicture"]);
|
||||
.limit(limit);
|
||||
|
||||
// Populate adoptedBy information
|
||||
for (const street of streets) {
|
||||
if (street.adoptedBy && street.adoptedBy.userId) {
|
||||
await street.populate("adoptedBy");
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount = await Street.countDocuments();
|
||||
|
||||
@@ -47,6 +49,12 @@ router.get(
|
||||
if (!street) {
|
||||
return res.status(404).json({ msg: "Street not found" });
|
||||
}
|
||||
|
||||
// Populate adoptedBy information if exists
|
||||
if (street.adoptedBy && street.adoptedBy.userId) {
|
||||
await street.populate("adoptedBy");
|
||||
}
|
||||
|
||||
res.json(street);
|
||||
}),
|
||||
);
|
||||
@@ -59,12 +67,11 @@ router.post(
|
||||
asyncHandler(async (req, res) => {
|
||||
const { name, location } = req.body;
|
||||
|
||||
const newStreet = new Street({
|
||||
const street = await Street.create({
|
||||
name,
|
||||
location,
|
||||
});
|
||||
|
||||
const street = await newStreet.save();
|
||||
res.json(street);
|
||||
}),
|
||||
);
|
||||
@@ -75,64 +82,63 @@ router.put(
|
||||
auth,
|
||||
streetIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
|
||||
try {
|
||||
const street = await Street.findById(req.params.id).session(session);
|
||||
await couchdbService.initialize();
|
||||
|
||||
const street = await Street.findById(req.params.id);
|
||||
if (!street) {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
return res.status(404).json({ msg: "Street not found" });
|
||||
}
|
||||
|
||||
if (street.status === "adopted") {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
return res.status(400).json({ msg: "Street already adopted" });
|
||||
}
|
||||
|
||||
// Check if user has already adopted this street
|
||||
const user = await User.findById(req.user.id).session(session);
|
||||
const user = await User.findById(req.user.id);
|
||||
if (user.adoptedStreets.includes(req.params.id)) {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
return res
|
||||
.status(400)
|
||||
.json({ msg: "You have already adopted this street" });
|
||||
}
|
||||
|
||||
// Get user details for embedding
|
||||
const userDetails = {
|
||||
userId: user._id,
|
||||
name: user.name,
|
||||
profilePicture: user.profilePicture || ''
|
||||
};
|
||||
|
||||
// Update street
|
||||
street.adoptedBy = req.user.id;
|
||||
street.adoptedBy = userDetails;
|
||||
street.status = "adopted";
|
||||
await street.save({ session });
|
||||
await street.save();
|
||||
|
||||
// Update user's adoptedStreets array
|
||||
user.adoptedStreets.push(street._id);
|
||||
await user.save({ session });
|
||||
user.stats.streetsAdopted = user.adoptedStreets.length;
|
||||
await user.save();
|
||||
|
||||
// Award points for street adoption
|
||||
const { transaction } = await awardStreetAdoptionPoints(
|
||||
// Award points for street adoption using CouchDB service
|
||||
const updatedUser = await couchdbService.updateUserPoints(
|
||||
req.user.id,
|
||||
street._id,
|
||||
session,
|
||||
50,
|
||||
'Street adoption',
|
||||
{
|
||||
entityType: 'Street',
|
||||
entityId: street._id,
|
||||
entityName: street.name
|
||||
}
|
||||
);
|
||||
|
||||
// Check and award badges
|
||||
const newBadges = await checkAndAwardBadges(req.user.id, session);
|
||||
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
|
||||
res.json({
|
||||
street,
|
||||
pointsAwarded: transaction.amount,
|
||||
newBalance: transaction.balanceAfter,
|
||||
badgesEarned: newBadges,
|
||||
pointsAwarded: 50,
|
||||
newBalance: updatedUser.points,
|
||||
badgesEarned: [], // Badges are handled automatically in CouchDB service
|
||||
});
|
||||
} catch (err) {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
console.error("Error adopting street:", err.message);
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
const express = require("express");
|
||||
const mongoose = require("mongoose");
|
||||
const Task = require("../models/Task");
|
||||
const User = require("../models/User");
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
const auth = require("../middleware/auth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
createTaskValidation,
|
||||
taskIdValidation,
|
||||
} = require("../middleware/validators/taskValidator");
|
||||
const {
|
||||
awardTaskCompletionPoints,
|
||||
checkAndAwardBadges,
|
||||
} = require("../services/gamificationService");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -20,7 +16,7 @@ router.get(
|
||||
"/",
|
||||
auth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
|
||||
const { buildPaginatedResponse } = require("../middleware/pagination");
|
||||
|
||||
// Parse pagination params
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
@@ -28,11 +24,19 @@ router.get(
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const tasks = await Task.find({ completedBy: req.user.id })
|
||||
.sort({ createdAt: -1 })
|
||||
.sort([{ createdAt: "desc" }])
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.populate("street", ["name"])
|
||||
.populate("completedBy", ["name"]);
|
||||
.limit(limit);
|
||||
|
||||
// Populate street and completedBy information
|
||||
for (const task of tasks) {
|
||||
if (task.street && task.street.streetId) {
|
||||
await task.populate("street");
|
||||
}
|
||||
if (task.completedBy && task.completedBy.userId) {
|
||||
await task.populate("completedBy");
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount = await Task.countDocuments({ completedBy: req.user.id });
|
||||
|
||||
@@ -48,12 +52,25 @@ router.post(
|
||||
asyncHandler(async (req, res) => {
|
||||
const { street, description } = req.body;
|
||||
|
||||
const newTask = new Task({
|
||||
street,
|
||||
// Get street details for embedding
|
||||
const Street = require("./Street");
|
||||
const streetDoc = await Street.findById(street);
|
||||
|
||||
if (!streetDoc) {
|
||||
return res.status(404).json({ msg: "Street not found" });
|
||||
}
|
||||
|
||||
const streetData = {
|
||||
streetId: streetDoc._id,
|
||||
name: streetDoc.name,
|
||||
location: streetDoc.location
|
||||
};
|
||||
|
||||
const task = await Task.create({
|
||||
street: streetData,
|
||||
description,
|
||||
});
|
||||
|
||||
const task = await newTask.save();
|
||||
res.json(task);
|
||||
}),
|
||||
);
|
||||
@@ -64,58 +81,53 @@ router.put(
|
||||
auth,
|
||||
taskIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const session = await mongoose.startSession();
|
||||
session.startTransaction();
|
||||
|
||||
try {
|
||||
const task = await Task.findById(req.params.id).session(session);
|
||||
await couchdbService.initialize();
|
||||
|
||||
const task = await Task.findById(req.params.id);
|
||||
if (!task) {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
return res.status(404).json({ msg: "Task not found" });
|
||||
}
|
||||
|
||||
// Check if task is already completed
|
||||
if (task.status === "completed") {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
return res.status(400).json({ msg: "Task already completed" });
|
||||
}
|
||||
|
||||
// Get user details for embedding
|
||||
const user = await User.findById(req.user.id);
|
||||
const userDetails = {
|
||||
userId: user._id,
|
||||
name: user.name,
|
||||
profilePicture: user.profilePicture || ''
|
||||
};
|
||||
|
||||
// Update task
|
||||
task.completedBy = req.user.id;
|
||||
task.completedBy = userDetails;
|
||||
task.status = "completed";
|
||||
await task.save({ session });
|
||||
task.completedAt = new Date().toISOString();
|
||||
await task.save();
|
||||
|
||||
// Update user's completedTasks array
|
||||
const user = await User.findById(req.user.id).session(session);
|
||||
if (!user.completedTasks.includes(task._id)) {
|
||||
user.completedTasks.push(task._id);
|
||||
await user.save({ session });
|
||||
}
|
||||
|
||||
// Award points for task completion
|
||||
const { transaction } = await awardTaskCompletionPoints(
|
||||
// Award points for task completion using CouchDB service
|
||||
const updatedUser = await couchdbService.updateUserPoints(
|
||||
req.user.id,
|
||||
task._id,
|
||||
session,
|
||||
task.pointsAwarded || 10,
|
||||
`Completed task: ${task.description}`,
|
||||
{
|
||||
entityType: 'Task',
|
||||
entityId: task._id,
|
||||
entityName: task.description
|
||||
}
|
||||
);
|
||||
|
||||
// Check and award badges
|
||||
const newBadges = await checkAndAwardBadges(req.user.id, session);
|
||||
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
|
||||
res.json({
|
||||
task,
|
||||
pointsAwarded: transaction.amount,
|
||||
newBalance: transaction.balanceAfter,
|
||||
badgesEarned: newBadges,
|
||||
pointsAwarded: task.pointsAwarded || 10,
|
||||
newBalance: updatedUser.points,
|
||||
badgesEarned: [], // Badges are handled automatically in CouchDB service
|
||||
});
|
||||
} catch (err) {
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
console.error("Error completing task:", err.message);
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user