started dockerfiles and itemrequest

This commit is contained in:
jackiettran
2025-08-18 12:24:46 -04:00
parent 209d8f5fbf
commit 99eae4774e
18 changed files with 2080 additions and 1 deletions

15
backend/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN mkdir -p uploads/profiles
EXPOSE 5000
CMD ["node", "server.js"]

View File

@@ -0,0 +1,76 @@
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const ItemRequest = sequelize.define('ItemRequest', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false
},
address1: {
type: DataTypes.STRING
},
address2: {
type: DataTypes.STRING
},
city: {
type: DataTypes.STRING
},
state: {
type: DataTypes.STRING
},
zipCode: {
type: DataTypes.STRING
},
country: {
type: DataTypes.STRING
},
latitude: {
type: DataTypes.DECIMAL(10, 8)
},
longitude: {
type: DataTypes.DECIMAL(11, 8)
},
maxPricePerHour: {
type: DataTypes.DECIMAL(10, 2)
},
maxPricePerDay: {
type: DataTypes.DECIMAL(10, 2)
},
preferredStartDate: {
type: DataTypes.DATE
},
preferredEndDate: {
type: DataTypes.DATE
},
isFlexibleDates: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
status: {
type: DataTypes.ENUM('open', 'fulfilled', 'closed'),
defaultValue: 'open'
},
requesterId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
responseCount: {
type: DataTypes.INTEGER,
defaultValue: 0
}
});
module.exports = ItemRequest;

View File

@@ -0,0 +1,59 @@
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const ItemRequestResponse = sequelize.define('ItemRequestResponse', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
itemRequestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'ItemRequests',
key: 'id'
}
},
responderId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
message: {
type: DataTypes.TEXT,
allowNull: false
},
offerPricePerHour: {
type: DataTypes.DECIMAL(10, 2)
},
offerPricePerDay: {
type: DataTypes.DECIMAL(10, 2)
},
availableStartDate: {
type: DataTypes.DATE
},
availableEndDate: {
type: DataTypes.DATE
},
existingItemId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'Items',
key: 'id'
}
},
status: {
type: DataTypes.ENUM('pending', 'accepted', 'declined', 'expired'),
defaultValue: 'pending'
},
contactInfo: {
type: DataTypes.STRING
}
});
module.exports = ItemRequestResponse;

View File

@@ -3,6 +3,8 @@ const User = require('./User');
const Item = require('./Item');
const Rental = require('./Rental');
const Message = require('./Message');
const ItemRequest = require('./ItemRequest');
const ItemRequestResponse = require('./ItemRequestResponse');
User.hasMany(Item, { as: 'ownedItems', foreignKey: 'ownerId' });
Item.belongsTo(User, { as: 'owner', foreignKey: 'ownerId' });
@@ -22,10 +24,21 @@ Message.belongsTo(User, { as: 'receiver', foreignKey: 'receiverId' });
Message.hasMany(Message, { as: 'replies', foreignKey: 'parentMessageId' });
Message.belongsTo(Message, { as: 'parentMessage', foreignKey: 'parentMessageId' });
User.hasMany(ItemRequest, { as: 'itemRequests', foreignKey: 'requesterId' });
ItemRequest.belongsTo(User, { as: 'requester', foreignKey: 'requesterId' });
User.hasMany(ItemRequestResponse, { as: 'itemRequestResponses', foreignKey: 'responderId' });
ItemRequest.hasMany(ItemRequestResponse, { as: 'responses', foreignKey: 'itemRequestId' });
ItemRequestResponse.belongsTo(User, { as: 'responder', foreignKey: 'responderId' });
ItemRequestResponse.belongsTo(ItemRequest, { as: 'itemRequest', foreignKey: 'itemRequestId' });
ItemRequestResponse.belongsTo(Item, { as: 'existingItem', foreignKey: 'existingItemId' });
module.exports = {
sequelize,
User,
Item,
Rental,
Message
Message,
ItemRequest,
ItemRequestResponse
};

View File

@@ -0,0 +1,286 @@
const express = require('express');
const { Op } = require('sequelize');
const { ItemRequest, ItemRequestResponse, User, Item } = require('../models');
const { authenticateToken } = require('../middleware/auth');
const router = express.Router();
router.get('/', async (req, res) => {
try {
const {
search,
status = 'open',
page = 1,
limit = 20
} = req.query;
const where = { status };
if (search) {
where[Op.or] = [
{ title: { [Op.iLike]: `%${search}%` } },
{ description: { [Op.iLike]: `%${search}%` } }
];
}
const offset = (page - 1) * limit;
const { count, rows } = await ItemRequest.findAndCountAll({
where,
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
}
],
limit: parseInt(limit),
offset: parseInt(offset),
order: [['createdAt', 'DESC']]
});
res.json({
requests: rows,
totalPages: Math.ceil(count / limit),
currentPage: parseInt(page),
totalRequests: count
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/my-requests', authenticateToken, async (req, res) => {
try {
const requests = await ItemRequest.findAll({
where: { requesterId: req.user.id },
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: ItemRequestResponse,
as: 'responses',
include: [
{
model: User,
as: 'responder',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: Item,
as: 'existingItem'
}
]
}
],
order: [['createdAt', 'DESC']]
});
res.json(requests);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/:id', async (req, res) => {
try {
const request = await ItemRequest.findByPk(req.params.id, {
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: ItemRequestResponse,
as: 'responses',
include: [
{
model: User,
as: 'responder',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: Item,
as: 'existingItem'
}
]
}
]
});
if (!request) {
return res.status(404).json({ error: 'Item request not found' });
}
res.json(request);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/', authenticateToken, async (req, res) => {
try {
const request = await ItemRequest.create({
...req.body,
requesterId: req.user.id
});
const requestWithRequester = await ItemRequest.findByPk(request.id, {
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
}
]
});
res.status(201).json(requestWithRequester);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.put('/:id', authenticateToken, async (req, res) => {
try {
const request = await ItemRequest.findByPk(req.params.id);
if (!request) {
return res.status(404).json({ error: 'Item request not found' });
}
if (request.requesterId !== req.user.id) {
return res.status(403).json({ error: 'Unauthorized' });
}
await request.update(req.body);
const updatedRequest = await ItemRequest.findByPk(request.id, {
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
}
]
});
res.json(updatedRequest);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.delete('/:id', authenticateToken, async (req, res) => {
try {
const request = await ItemRequest.findByPk(req.params.id);
if (!request) {
return res.status(404).json({ error: 'Item request not found' });
}
if (request.requesterId !== req.user.id) {
return res.status(403).json({ error: 'Unauthorized' });
}
await request.destroy();
res.status(204).send();
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/:id/responses', authenticateToken, async (req, res) => {
try {
const request = await ItemRequest.findByPk(req.params.id);
if (!request) {
return res.status(404).json({ error: 'Item request not found' });
}
if (request.requesterId === req.user.id) {
return res.status(400).json({ error: 'Cannot respond to your own request' });
}
if (request.status !== 'open') {
return res.status(400).json({ error: 'Cannot respond to closed request' });
}
const response = await ItemRequestResponse.create({
...req.body,
itemRequestId: req.params.id,
responderId: req.user.id
});
await request.increment('responseCount');
const responseWithDetails = await ItemRequestResponse.findByPk(response.id, {
include: [
{
model: User,
as: 'responder',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: Item,
as: 'existingItem'
}
]
});
res.status(201).json(responseWithDetails);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.put('/responses/:responseId/status', authenticateToken, async (req, res) => {
try {
const { status } = req.body;
const response = await ItemRequestResponse.findByPk(req.params.responseId, {
include: [
{
model: ItemRequest,
as: 'itemRequest'
}
]
});
if (!response) {
return res.status(404).json({ error: 'Response not found' });
}
if (response.itemRequest.requesterId !== req.user.id) {
return res.status(403).json({ error: 'Only the requester can update response status' });
}
await response.update({ status });
if (status === 'accepted') {
await response.itemRequest.update({ status: 'fulfilled' });
}
const updatedResponse = await ItemRequestResponse.findByPk(response.id, {
include: [
{
model: User,
as: 'responder',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: Item,
as: 'existingItem'
}
]
});
res.json(updatedResponse);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;

View File

@@ -18,6 +18,7 @@ const itemRoutes = require("./routes/items");
const rentalRoutes = require("./routes/rentals");
const messageRoutes = require("./routes/messages");
const betaRoutes = require("./routes/beta");
const itemRequestRoutes = require("./routes/itemRequests");
const app = express();
@@ -37,6 +38,7 @@ app.use("/api/users", userRoutes);
app.use("/api/items", itemRoutes);
app.use("/api/rentals", rentalRoutes);
app.use("/api/messages", messageRoutes);
app.use("/api/item-requests", itemRequestRoutes);
app.get("/", (req, res) => {
res.json({ message: "CommunityRentals.App API is running!" });