diff --git a/README.md b/README.md index c66865f..b344e4c 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,15 @@ +
+🚀 Redis Caching (Optional) + +1. You can speed up responses and save API quotas by enabling Redis caching. +2. Provide your Redis connection URL (e.g., from Redis Labs, Upstash, or local server). +3. Save it as `REDIS_URL` in your `.env` file (e.g., `REDIS_URL=redis://localhost:6379`) + +
+ --- ## ⚙️ Setup & Installation @@ -98,7 +107,7 @@ cd Cybershield_URL # 2. Configure environment variables cp .env.example .env -# Open .env and fill in API_KEY and GEMINI_API_KEY +# Open .env and fill in API_KEY, GEMINI_API_KEY, and (optionally) REDIS_URL # 3. Start the backend node server.js diff --git a/package-lock.json b/package-lock.json index 2e2ef3e..1185bd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,80 @@ "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", - "node-fetch": "^2.7.0" + "node-fetch": "^2.7.0", + "redis": "^6.1.0" + } + }, + "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/accepts": { @@ -90,6 +163,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -683,6 +765,22 @@ "node": ">= 0.10" } }, + "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/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/package.json b/package.json index c5a6b35..def11a6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", - "node-fetch": "^2.7.0" + "node-fetch": "^2.7.0", + "redis": "^6.1.0" } } diff --git a/server.js b/server.js index 4f41058..9440dd5 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,7 @@ const express = require('express'); const cors = require('cors'); const dotenv = require('dotenv'); +const { createClient } = require('redis'); dotenv.config(); console.log("API_KEY:", process.env.API_KEY); @@ -9,6 +10,21 @@ console.log("GEMINI_API_KEY:", process.env.GEMINI_API_KEY); const app = express(); const PORT = process.env.PORT || 3000; +// ─── Redis Setup ─── +let redisClient = null; +if (process.env.REDIS_URL) { + redisClient = createClient({ url: process.env.REDIS_URL }); + + redisClient.on('error', (err) => console.warn('[REDIS ERROR]', err.message)); + + redisClient.connect().then(() => { + console.log('[REDIS] Connected successfully'); + }).catch(err => { + console.warn('[REDIS] Initial connection failed:', err.message); + redisClient = null; + }); +} + // ─── CORS ─── const ALLOWED_ORIGINS = process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(',').map(o => o.trim()) @@ -75,6 +91,21 @@ app.post('/check', async (req, res) => { console.log(`[SCAN] ${userUrl}`); + // ─── Check Cache ─── + if (redisClient && redisClient.isOpen) { + try { + const cachedResult = await redisClient.get(userUrl); + if (cachedResult) { + console.log(`[CACHE HIT] ${userUrl}`); + const data = JSON.parse(cachedResult); + data.cached = true; + return res.json(data); + } + } catch (err) { + console.warn('[REDIS GET ERROR]', err.message); + } + } + const body = { client: { clientId: 'cybershield', clientVersion: '2.0' }, threatInfo: { @@ -99,6 +130,16 @@ app.post('/check', async (req, res) => { const data = await response.json(); console.log(`[RESULT] Matches: ${data.matches ? data.matches.length : 0}`); + + // ─── Set Cache ─── + if (redisClient && redisClient.isOpen) { + try { + await redisClient.setEx(userUrl, 86400, JSON.stringify(data)); + } catch (err) { + console.warn('[REDIS SET ERROR]', err.message); + } + } + res.json(data); } catch (err) {