Skip to content
Open
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
73 changes: 73 additions & 0 deletions Middleware/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const redisClient = require('../config/redis');

/**
* Cache middleware for GET requests
* @param {number} duration - Cache duration in seconds (default: 3600 = 1 hour)
*/
const cache = (duration = 3600) => {
return async (req, res, next) => {
if (req.method !== 'GET') {
return next();
}

try {
const client = redisClient.getClient();
// Include userId in cache key for user-specific routes
const key = req.user
? `cache:${req.originalUrl || req.url}:${req.user.id}`
: `cache:${req.originalUrl || req.url}`;

const cachedData = await client.get(key);
if (cachedData) {
console.log(`Cache HIT: ${key}`);
return res.json(JSON.parse(cachedData));
}

console.log(`Cache MISS: ${key}`);
const originalJson = res.json.bind(res);
res.json = (data) => {
client.setEx(key, duration, JSON.stringify(data))
.catch(err => console.error('Cache set error:', err));
return originalJson(data);
};

next();
} catch (error) {
console.error('Cache middleware error:', error);
next();
}
};
};

/**
* Clear cache by pattern
* @param {string} pattern - Redis key pattern (e.g., 'cache:users*')
*/
const clearCache = async (pattern) => {
try {
const client = redisClient.getClient();
const keys = await client.keys(pattern);
if (keys.length > 0) {
await client.del(keys);
console.log(`Cleared ${keys.length} cache keys matching: ${pattern}`);
}
} catch (error) {
console.error('Clear cache error:', error);
}
};

/**
* Clear specific cache key
* @param {string} key
*/
const clearCacheKey = async (key) => {
try {
const client = redisClient.getClient();
await client.del(key);
console.log(`Cleared cache key: ${key}`);
} catch (error) {
console.error('Clear cache key error:', error);
}
};

module.exports = { cache, clearCache, clearCacheKey };
12 changes: 12 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ListingRouter = require('./router/listingRouter');
const MarketPlaceRouter = require('./router/marketplaceRouter');
const notificationRouter = require('./router/notificationRouter');
const { protect, restrictTo } = require('./controllers/authController');
const redisClient = require('./config/redis');

//development-logging
if (process.env.NODE_ENV === 'development') {
Expand All @@ -19,6 +20,17 @@ if (process.env.NODE_ENV === 'development') {
app.use(express.json());
app.use(cookiesParser());

// Initialize Redis connection
redisClient.connect()
.then(() => console.log('Redis connected successfully'))
.catch(err => console.error('Redis connection failed:', err));

// Handle graceful shutdown
process.on('SIGINT', async () => {
await redisClient.disconnect();
process.exit(0);
});

//routes//
app.use('/api/v1/listings', ListingRouter);
app.use('/api/v1/marketplace/listings', MarketPlaceRouter);
Expand Down
70 changes: 70 additions & 0 deletions config/redis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

const redis = require('redis');

class RedisClient {
constructor() {
this.client = null;
this.isConnected = false;
}

async connect() {
try {
this.client = redis.createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379',
password: process.env.REDIS_PASSWORD,
socket: {
reconnectStrategy: (retries) => {
if (retries > 10) {
console.error('Redis: Max reconnection attempts reached');
return new Error('Max reconnection attempts reached');
}
return Math.min(retries * 100, 3000);
}
}
});

this.client.on('error', (err) => {
console.error('Redis Client Error:', err);
this.isConnected = false;
});

this.client.on('connect', () => {
console.log('Redis: Connecting...');
});

this.client.on('ready', () => {
console.log('Redis: Connected and ready');
this.isConnected = true;
});

this.client.on('reconnecting', () => {
console.log('Redis: Reconnecting...');
});

await this.client.connect();
return this.client;
} catch (error) {
console.error('Failed to connect to Redis:', error);
throw error;
}
}

getClient() {
if (!this.isConnected) {
throw new Error('Redis client is not connected');
}
return this.client;
}

async disconnect() {
if (this.client) {
await this.client.quit();
this.isConnected = false;
console.log('Redis: Disconnected');
}
}
}

const redisClient = new RedisClient();

module.exports = redisClient;
2 changes: 1 addition & 1 deletion controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ exports.protect = catchAsync(async (req, res, next) => {
const user = await User.findByPk(decoded.user_id);
if (!user)
return next(
new AppError('The user belonginf to this token does not exist', 401)
new AppError('The user belonging to this token does not exist', 401)
);
//check if user is verified
if (!user.is_verified)
Expand Down
105 changes: 67 additions & 38 deletions controllers/listingController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const AppError = require("../utils/appError");
const catchAsync = require("../utils/catchAsync");
const { Listing, MarketplaceListing, User } = require("../db/models");
const { where } = require("sequelize");
const { clearCache, clearCacheKey } = require('../Middleware/cache');

exports.createListing = catchAsync(async (req, res, next) => {
const payload = {
Expand All @@ -19,7 +19,6 @@ exports.createListing = catchAsync(async (req, res, next) => {

const listing = await Listing.create(payload);
if (!listing) return next(new AppError("Error creating listing", 500));
// console.log("Listing created successfully:", listing.toJSON());

// create a market place listing, if listing is successful
const marketplace_listing = await MarketplaceListing.create({
Expand All @@ -29,6 +28,16 @@ exports.createListing = catchAsync(async (req, res, next) => {
if (!marketplace_listing)
return next(new AppError("Error creating marketplaceListing", 500));

// Clear relevant caches
try {
await clearCacheKey(`cache:/api/v1/listings:${req.user.id}`); // getAllListings
await clearCacheKey(`cache:/api/v1/listings/${listing.id}:${req.user.id}`); // getListingById
await clearCacheKey(`cache:/api/v1/listings/listingstats:${req.user.id}`); // getUserListingData
await clearCache('cache:/api/v1/marketplace*'); // Marketplace listings
} catch (err) {
console.error('Redis cache clear error:', err);
}

res.status(201).json({
status: "success",
data: listing,
Expand Down Expand Up @@ -100,11 +109,22 @@ exports.deleteListing = catchAsync(async (req, res, next) => {
}

if (!marketplace) {
return next(new AppError("No marketplace found with that ID"));
return next(new AppError("No marketplace found with that ID", 404));
}

await marketplace.destroy();
await listing.destroy();

// Clear relevant caches
try {
await clearCacheKey(`cache:/api/v1/listings:${req.user.id}`);
await clearCacheKey(`cache:/api/v1/listings/${id}:${req.user.id}`);
await clearCacheKey(`cache:/api/v1/listings/listingstats:${req.user.id}`);
await clearCache('cache:/api/v1/marketplace*');
} catch (err) {
console.error('Redis cache clear error:', err);
}

res.status(204).json({
status: "success",
data: null,
Expand All @@ -115,65 +135,74 @@ exports.updateListingStatus = catchAsync(async (req, res, next) => {
const { id } = req.params;
const { status } = req.query;

try {
const allowedStatuses = [
"pending",
"accepted",
"in-progress",
"completed",
"cancelled",
];

if (!allowedStatuses.includes(status)) {
return next(
new AppError(
`Invalid status. Allowed values: ${allowedStatuses.join(", ")}`,
400
)
);
}

const listing = await Listing.findByPk(id);
if (!listing) {
return next(new AppError("Listing not found", 404));
}

listing.status = status;
await listing.save();
const allowedStatuses = [
"pending",
"accepted",
"in-progress",
"completed",
"cancelled",
];

if (!allowedStatuses.includes(status)) {
return next(
new AppError(
`Invalid status. Allowed values: ${allowedStatuses.join(", ")}`,
400
)
);
}

res.status(200).json({
status: "success",
message: `Listing status updated to: '${status}' `,
data: {
id: listing.id,
status: listing.status,
},
});
const listing = await Listing.findByPk(id);
if (!listing) {
return next(new AppError("Listing not found", 404));
}

listing.status = status;
await listing.save();

// Clear relevant caches
try {
await clearCacheKey(`cache:/api/v1/listings:${req.user.id}`);
await clearCacheKey(`cache:/api/v1/listings/${id}:${req.user.id}`);
await clearCacheKey(`cache:/api/v1/listings/listingstats:${req.user.id}`);
await clearCache('cache:/api/v1/marketplace/listings*');
} catch (err) {
return next(new AppError(err.message, 500));
console.error('Redis cache clear error:', err);
}

res.status(200).json({
status: "success",
message: `Listing status updated to: '${status}'`,
data: {
id: listing.id,
status: listing.status,
},
});
});

exports.getUserListingData = catchAsync(async (req, res, next) => {
const listingdata = await Listing.findAll({
where: { user_id_id: req.user.id },
});

const totalcompletedlisting = await Listing.findAll({
where: {
user_id_id: req.user.id,
status: "completed",
},
});

const recentListing = await Listing.findAll({
where: {
user_id_id: req.user.id,
},
order: [['created_at', 'DESC']],
limit: 5,
});

res.status(200).json({
total_waste_posted: listingdata.length,
total_waste_completed: totalcompletedlisting.length,
recent_listing: recentListing,
});
});
});
Loading