Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/backend/models/ConnectionRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ const connectionRequestSchema = new mongoose.Schema({
}
});

// Prevent duplicate requests
connectionRequestSchema.index({ from: 1, to: 1 }, { unique: true });
// Index for efficient querying (removed unique constraint to allow resending after rejection)
connectionRequestSchema.index({ from: 1, to: 1 });
connectionRequestSchema.index({ from: 1, to: 1, status: 1 });

// Update the updatedAt field before saving
connectionRequestSchema.pre('save', function(next) {
Expand Down
15 changes: 15 additions & 0 deletions lib/backend/models/Event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const mongoose = require('mongoose');

const eventSchema = new mongoose.Schema(
{
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
date: { type: Date, required: true, index: true }, // store date at 00:00 UTC
title: { type: String, required: true, trim: true },
},
{ timestamps: true }
);

// Index for efficient querying by user and date
eventSchema.index({ user: 1, date: 1 });

module.exports = mongoose.model('Event', eventSchema);
27 changes: 12 additions & 15 deletions lib/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
{
"name": "barter-system-backend",
"name": "barter-backend",
"version": "1.0.0",
"description": "Backend for Barter System",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
"dev": "nodemon server.js",
"migrate": "node scripts/dropUniqueIndex.js"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"bcrypt": "^5.1.1",
"cloudinary": "^2.7.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"firebase-admin": "^13.5.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^7.5.0",
"multer": "^2.0.2",
"socket.io": "^4.8.1",
"zod": "^3.22.4"
"mongoose": "^7.0.0",
"cors": "^2.8.5",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.3",
"multer": "^1.4.5-lts.1",
"socket.io": "^4.7.2",
"firebase-admin": "^11.0.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
"nodemon": "^2.0.20"
}
}
}
93 changes: 89 additions & 4 deletions lib/backend/routes/connectionRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ router.post('/send', verifyToken, async (req, res) => {
});
}

// Check if request already exists
// Check if request already exists and is still pending
const existingRequest = await ConnectionRequest.findOne({
from: fromUserId,
to: toUserId
to: toUserId,
status: 'pending'
});

if (existingRequest) {
Expand All @@ -31,6 +32,21 @@ router.post('/send', verifyToken, async (req, res) => {
});
}

// Check if there's an accepted connection (they're already connected)
const acceptedRequest = await ConnectionRequest.findOne({
$or: [
{ from: fromUserId, to: toUserId, status: 'accepted' },
{ from: toUserId, to: fromUserId, status: 'accepted' }
]
});

if (acceptedRequest) {
return res.status(400).json({
success: false,
message: 'You are already connected with this user'
});
}

// Don't allow self-requests
if (fromUserId === toUserId) {
return res.status(400).json({
Expand Down Expand Up @@ -64,6 +80,15 @@ router.post('/send', verifyToken, async (req, res) => {

} catch (error) {
console.error('Error sending connection request:', error);

// Handle duplicate key error specifically
if (error.code === 11000) {
return res.status(400).json({
success: false,
message: 'Connection request already sent'
});
}

res.status(500).json({
success: false,
message: 'Internal server error'
Expand All @@ -76,16 +101,42 @@ router.get('/received', verifyToken, async (req, res) => {
try {
const userId = req.user.userId;

const requests = await ConnectionRequest.find({
// First, get all pending requests
const allPendingRequests = await ConnectionRequest.find({
to: userId,
status: 'pending'
})
.populate('from', 'name profileImage logo email')
.sort({ createdAt: -1 });

// Filter out requests from users who are already connected
const validRequests = [];

for (const request of allPendingRequests) {
const fromUserId = request.from._id;

// Check if there's an accepted connection between these users
const existingConnection = await ConnectionRequest.findOne({
$or: [
{ from: userId, to: fromUserId, status: 'accepted' },
{ from: fromUserId, to: userId, status: 'accepted' }
]
});

// Only include the request if users are not already connected
if (!existingConnection) {
validRequests.push(request);
} else {
// If users are already connected, mark this pending request as invalid
// and optionally delete it to clean up the database
console.log(`Removing invalid request from connected user: ${fromUserId}`);
await ConnectionRequest.findByIdAndDelete(request._id);
}
}

res.json({
success: true,
data: requests
data: validRequests
});

} catch (error) {
Expand Down Expand Up @@ -138,6 +189,23 @@ router.post('/accept/:requestId', verifyToken, async (req, res) => {
});
}

// Check if users are already connected (additional safety check)
const existingConnection = await ConnectionRequest.findOne({
$or: [
{ from: request.from, to: request.to, status: 'accepted' },
{ from: request.to, to: request.from, status: 'accepted' }
]
});

if (existingConnection) {
// Delete the invalid pending request
await ConnectionRequest.findByIdAndDelete(requestId);
return res.status(400).json({
success: false,
message: 'Users are already connected'
});
}

// Update request status
request.status = 'accepted';
await request.save();
Expand Down Expand Up @@ -196,6 +264,23 @@ router.post('/reject/:requestId', verifyToken, async (req, res) => {
});
}

// Check if users are already connected (additional safety check)
const existingConnection = await ConnectionRequest.findOne({
$or: [
{ from: request.from, to: request.to, status: 'accepted' },
{ from: request.to, to: request.from, status: 'accepted' }
]
});

if (existingConnection) {
// Delete the invalid pending request
await ConnectionRequest.findByIdAndDelete(requestId);
return res.status(400).json({
success: false,
message: 'Users are already connected'
});
}

// Update request status
request.status = 'rejected';
await request.save();
Expand Down
91 changes: 91 additions & 0 deletions lib/backend/routes/eventRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const express = require('express');
const router = express.Router();
const verifyToken = require('../middlewares/verifyToken');
const Event = require('../models/Event');

// Helper to normalize a date string (YYYY-MM-DD) to UTC midnight
function normalizeDate(dateStr) {
// Expecting 'YYYY-MM-DD' from client
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(Date.UTC(y, m - 1, d, 0, 0, 0, 0));
}

// Get events for a date
// GET /api/events?date=YYYY-MM-DD
router.get('/', verifyToken, async (req, res) => {
try {
const dateStr = req.query.date;
if (!dateStr) return res.status(400).json({ message: 'date (YYYY-MM-DD) is required' });

const date = normalizeDate(dateStr);
const nextDate = new Date(date);
nextDate.setUTCDate(nextDate.getUTCDate() + 1);

const events = await Event.find({
user: req.user.userId,
date: { $gte: date, $lt: nextDate },
}).sort({ createdAt: 1 });

res.json(events);
} catch (err) {
console.error('GET /api/events error', err);
res.status(500).json({ message: 'Server error' });
}
});

// Create event
// POST /api/events { date: 'YYYY-MM-DD', title: string }
router.post('/', verifyToken, async (req, res) => {
try {
const { date: dateStr, title } = req.body;
if (!dateStr || !title) return res.status(400).json({ message: 'date and title are required' });

const date = normalizeDate(dateStr);
const event = await Event.create({ user: req.user.userId, date, title });
res.status(201).json(event);
} catch (err) {
console.error('POST /api/events error', err);
res.status(500).json({ message: 'Server error' });
}
});

// Update event (title)
// PATCH /api/events/:id { title }
router.patch('/:id', verifyToken, async (req, res) => {
try {
const { id } = req.params;
const { title } = req.body;

if (typeof title !== 'string' || !title.trim()) {
return res.status(400).json({ message: 'title is required' });
}

const updated = await Event.findOneAndUpdate(
{ _id: id, user: req.user.userId },
{ $set: { title: title.trim() } },
{ new: true }
);

if (!updated) return res.status(404).json({ message: 'Event not found' });
res.json(updated);
} catch (err) {
console.error('PATCH /api/events/:id error', err);
res.status(500).json({ message: 'Server error' });
}
});

// Delete event
// DELETE /api/events/:id
router.delete('/:id', verifyToken, async (req, res) => {
try {
const { id } = req.params;
const deleted = await Event.findOneAndDelete({ _id: id, user: req.user.userId });
if (!deleted) return res.status(404).json({ message: 'Event not found' });
res.json({ success: true });
} catch (err) {
console.error('DELETE /api/events/:id error', err);
res.status(500).json({ message: 'Server error' });
}
});

module.exports = router;
6 changes: 3 additions & 3 deletions lib/backend/routes/skillMatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ router.get('/match', async (req, res) => {
return res.status(400).json({ msg: 'Required and offered skills are needed' });
}

// Find users with complementary skills
// Find users with complementary skills (case-sensitive matching)
// User offers what we need AND needs what we offer
const users = await User.find({
$and: [
{ skillsOffered: { $regex: required, $options: 'i' } },
{ skillsRequired: { $regex: offered, $options: 'i' } }
{ skillsOffered: { $regex: required, $options: '' } }, // Remove 'i' flag for case-sensitive
{ skillsRequired: { $regex: offered, $options: '' } } // Remove 'i' flag for case-sensitive
]
}).select('_id name profileImage education location profession skillsOffered skillsRequired logo');

Expand Down
57 changes: 57 additions & 0 deletions lib/backend/scripts/dropUniqueIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const mongoose = require('mongoose');

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/barter', {
useNewUrlParser: true,
useUnifiedTopology: true,
});

const db = mongoose.connection;

db.once('open', async () => {
try {
console.log('Connected to MongoDB');

// Drop the unique index on from_1_to_1
const collection = db.collection('connectionrequests');

try {
await collection.dropIndex('from_1_to_1');
console.log('✅ Successfully dropped unique index: from_1_to_1');
} catch (error) {
if (error.code === 27) {
console.log('ℹ️ Index from_1_to_1 does not exist (already dropped)');
} else {
console.error('❌ Error dropping index:', error);
}
}

// Create new non-unique indexes
try {
await collection.createIndex({ from: 1, to: 1 });
console.log('✅ Created new non-unique index: from_1_to_1');
} catch (error) {
console.error('❌ Error creating new index:', error);
}

try {
await collection.createIndex({ from: 1, to: 1, status: 1 });
console.log('✅ Created new compound index: from_1_to_1_status_1');
} catch (error) {
console.error('❌ Error creating compound index:', error);
}

console.log('🎉 Database migration completed successfully!');
console.log('Now users can send new connection requests after rejection.');

process.exit(0);
} catch (error) {
console.error('❌ Migration failed:', error);
process.exit(1);
}
});

db.on('error', (error) => {
console.error('❌ MongoDB connection error:', error);
process.exit(1);
});
4 changes: 4 additions & 0 deletions lib/backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const connectionRoutes = require('./routes/connectionRoutes.js'); // Connection
const notificationRoutes = require('./routes/notificationRoutes.js'); // Notification routes
const reviewRoutes = require('./routes/reviewRoutes.js'); // Review routes
const todoRoutes = require('./routes/todoRoutes.js'); // Todo routes
const eventRoutes = require('./routes/eventRoutes.js'); // Event routes
const cors = require('cors');

const Message = require('./models/message.js'); // Import the Message model
Expand Down Expand Up @@ -93,6 +94,9 @@ app.use('/api/reviews', reviewRoutes);
// Register todo routes
app.use('/api/todos', todoRoutes);

// Register event routes
app.use('/api/events', eventRoutes);

// Export io for use in other modules
module.exports = { io };

Expand Down
Loading
Loading