diff --git a/server/package-lock.json b/server/package-lock.json index cee8002..64317e4 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -18,6 +18,7 @@ "mongoose": "^8.3.1", "pdfkit": "^0.19.1", "qrcode": "^1.5.4", + "redis": "^6.1.0", "speakeasy": "^2.0.0", "winston": "^3.19.0" }, @@ -1229,6 +1230,78 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@redis/bloom": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.1.0.tgz", + "integrity": "sha512-Rzascjd9J9bJsM45T/Z9CTg1QY/B63B6YO8QorLVMeXnbBDsKiSCVR/+GQ061hYPk8FpTzWmPY8tAv2sT+JEtQ==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@redis/client": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.1.0.tgz", + "integrity": "sha512-7u1LefkezJF0HESlhO7ZFLEPfyY+NejP3SGv+Z4pGaT3oM5GVVLa0u3f4rDLUrcw+SRo8IlX9Y8JAONeDdg1Ag==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.1.0.tgz", + "integrity": "sha512-/GFjQA6bu5pG9ClCJAI5Xx4bNXe7UTpxBBlIupBNTrn1+nY860apGnYJuaSCDV2BmEbTidpa7O2qa28oxKx+rg==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@redis/search": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.1.0.tgz", + "integrity": "sha512-kS5agg+3yZbrdrt8omrew7FLCD8eOm7tarG1CROekPBRe+QGDR9aOpnHIQaYsYi6wPRTH70nQiF06AIjgURefQ==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, + "node_modules/@redis/time-series": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.1.0.tgz", + "integrity": "sha512-uIDBtV8MmG/xpJsRqbGSO4iX6ryj37MLMP82lRpFvI7ykAVe5GyqgxigEbU+uZNv9kDPNMKw3dvI/S/J1BNBzA==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "@redis/client": "^6.1.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -2051,6 +2124,15 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6058,6 +6140,22 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-6.1.0.tgz", + "integrity": "sha512-0kvUPM8RHP/ZMa0xYaDTcG5e8tIGW6kz6MToVT0V8iOnk6bkXp2jncGRGe2bZEk41lZwiDspUqjZCSk5ohjcKw==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "6.1.0", + "@redis/client": "6.1.0", + "@redis/json": "6.1.0", + "@redis/search": "6.1.0", + "@redis/time-series": "6.1.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/server/package.json b/server/package.json index 2effa0d..dd535f4 100644 --- a/server/package.json +++ b/server/package.json @@ -22,6 +22,7 @@ "mongoose": "^8.3.1", "pdfkit": "^0.19.1", "qrcode": "^1.5.4", + "redis": "^6.1.0", "speakeasy": "^2.0.0", "winston": "^3.19.0" }, diff --git a/server/routes/medicines.js b/server/routes/medicines.js index b56a85b..70930e3 100644 --- a/server/routes/medicines.js +++ b/server/routes/medicines.js @@ -10,12 +10,22 @@ const medicinesLimiter = rateLimit({ router.use(medicinesLimiter); const Medicine = require('../models/Medicine'); const authMiddleware = require('../middleware/authMiddleware'); +const redisClient = require('../utils/redisClient'); // @route GET /api/medicines // @desc Get all medicines for the logged-in user // @access Private router.get('/', authMiddleware, async (req, res) => { try { + const cacheKey = `meds_${req.user._id}`; + + if (redisClient.isConnected()) { + const cachedData = await redisClient.getClient().get(cacheKey); + if (cachedData) { + return res.json(JSON.parse(cachedData)); + } + } + const medicines = await Medicine.find({ user: { $eq: req.user._id } }).sort({ createdAt: 1 }); // Format medicines to match the frontend expectations: { id, name, time, date } @@ -26,6 +36,10 @@ router.get('/', authMiddleware, async (req, res) => { date: med.date, })); + if (redisClient.isConnected()) { + await redisClient.getClient().setEx(cacheKey, 300, JSON.stringify(formatted)); // Cache for 5 mins + } + res.json(formatted); } catch (err) { console.error('Fetch medicines error:', err.message); @@ -61,6 +75,10 @@ router.post('/', authMiddleware, async (req, res) => { time: newMedicine.time, date: newMedicine.date, }); + + if (redisClient.isConnected()) { + await redisClient.getClient().del(`meds_${req.user._id}`); + } } catch (err) { console.error('Add medicine error:', err.message); res.status(500).json({ message: 'Server error adding medicine reminder' }); @@ -80,6 +98,11 @@ router.delete('/:id', authMiddleware, async (req, res) => { } await medicine.deleteOne(); + + if (redisClient.isConnected()) { + await redisClient.getClient().del(`meds_${req.user._id}`); + } + res.json({ message: 'Medicine reminder deleted successfully', id: req.params.id }); } catch (err) { console.error('Delete medicine error:', err.message); diff --git a/server/utils/redisClient.js b/server/utils/redisClient.js new file mode 100644 index 0000000..e478b44 --- /dev/null +++ b/server/utils/redisClient.js @@ -0,0 +1,31 @@ +const redis = require('redis'); + +let redisClient; +let isRedisConnected = false; + +(async () => { + redisClient = redis.createClient({ + url: process.env.REDIS_URL || 'redis://127.0.0.1:6379' + }); + + redisClient.on('error', (error) => { + console.error(`Redis Error: ${error}`); + isRedisConnected = false; + }); + + redisClient.on('connect', () => { + console.log('Redis connected'); + isRedisConnected = true; + }); + + try { + await redisClient.connect(); + } catch (err) { + console.warn('Redis failed to connect initially. Falling back to DB queries.'); + } +})(); + +module.exports = { + getClient: () => redisClient, + isConnected: () => isRedisConnected +};