- Full-stack rental marketplace application - React frontend with TypeScript - Node.js/Express backend with JWT authentication - Features: item listings, rental requests, calendar availability, user profiles
164 lines
4.3 KiB
JavaScript
164 lines
4.3 KiB
JavaScript
const express = require('express');
|
|
const { Op } = require('sequelize');
|
|
const { Item, User, Rental } = require('../models'); // Import from models/index.js to get models with associations
|
|
const { authenticateToken } = require('../middleware/auth');
|
|
const router = express.Router();
|
|
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const {
|
|
tags,
|
|
isPortable,
|
|
minPrice,
|
|
maxPrice,
|
|
location,
|
|
search,
|
|
page = 1,
|
|
limit = 20
|
|
} = req.query;
|
|
|
|
const where = {};
|
|
|
|
if (tags) {
|
|
const tagsArray = Array.isArray(tags) ? tags : [tags];
|
|
where.tags = { [Op.overlap]: tagsArray };
|
|
}
|
|
if (isPortable !== undefined) where.isPortable = isPortable === 'true';
|
|
if (minPrice || maxPrice) {
|
|
where.pricePerDay = {};
|
|
if (minPrice) where.pricePerDay[Op.gte] = minPrice;
|
|
if (maxPrice) where.pricePerDay[Op.lte] = maxPrice;
|
|
}
|
|
if (location) where.location = { [Op.iLike]: `%${location}%` };
|
|
if (search) {
|
|
where[Op.or] = [
|
|
{ name: { [Op.iLike]: `%${search}%` } },
|
|
{ description: { [Op.iLike]: `%${search}%` } }
|
|
];
|
|
}
|
|
|
|
const offset = (page - 1) * limit;
|
|
|
|
const { count, rows } = await Item.findAndCountAll({
|
|
where,
|
|
include: [{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] }],
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset),
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
res.json({
|
|
items: rows,
|
|
totalPages: Math.ceil(count / limit),
|
|
currentPage: parseInt(page),
|
|
totalItems: count
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
router.get('/recommendations', authenticateToken, async (req, res) => {
|
|
try {
|
|
const userRentals = await Rental.findAll({
|
|
where: { renterId: req.user.id },
|
|
include: [{ model: Item, as: 'item' }]
|
|
});
|
|
|
|
const rentedTags = userRentals.reduce((tags, rental) => {
|
|
return [...tags, ...(rental.item.tags || [])];
|
|
}, []);
|
|
const uniqueTags = [...new Set(rentedTags)];
|
|
|
|
const recommendations = await Item.findAll({
|
|
where: {
|
|
tags: { [Op.overlap]: uniqueTags },
|
|
availability: true
|
|
},
|
|
limit: 10,
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
res.json(recommendations);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
router.get('/:id', async (req, res) => {
|
|
try {
|
|
const item = await Item.findByPk(req.params.id, {
|
|
include: [{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] }]
|
|
});
|
|
|
|
if (!item) {
|
|
return res.status(404).json({ error: 'Item not found' });
|
|
}
|
|
|
|
res.json(item);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
router.post('/', authenticateToken, async (req, res) => {
|
|
try {
|
|
const item = await Item.create({
|
|
...req.body,
|
|
ownerId: req.user.id
|
|
});
|
|
|
|
const itemWithOwner = await Item.findByPk(item.id, {
|
|
include: [{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] }]
|
|
});
|
|
|
|
res.status(201).json(itemWithOwner);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
router.put('/:id', authenticateToken, async (req, res) => {
|
|
try {
|
|
const item = await Item.findByPk(req.params.id);
|
|
|
|
if (!item) {
|
|
return res.status(404).json({ error: 'Item not found' });
|
|
}
|
|
|
|
if (item.ownerId !== req.user.id) {
|
|
return res.status(403).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
await item.update(req.body);
|
|
|
|
const updatedItem = await Item.findByPk(item.id, {
|
|
include: [{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] }]
|
|
});
|
|
|
|
res.json(updatedItem);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
router.delete('/:id', authenticateToken, async (req, res) => {
|
|
try {
|
|
const item = await Item.findByPk(req.params.id);
|
|
|
|
if (!item) {
|
|
return res.status(404).json({ error: 'Item not found' });
|
|
}
|
|
|
|
if (item.ownerId !== req.user.id) {
|
|
return res.status(403).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
await item.destroy();
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router; |