Skip to content

Commit ba2ad20

Browse files
Major updates before switching to axios
1 parent b670631 commit ba2ad20

30 files changed

+830
-532
lines changed

backend/helpers/verifyRecaptcha.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const fetch = require('node-fetch');
2+
3+
const RECAPTCHA_SECRET_KEY = process.env.RECAPTCHA_SECRET_KEY;
4+
5+
if (!RECAPTCHA_SECRET_KEY) throw new Error('Missing reCAPTCHA secret key.');
6+
7+
async function verifyRecaptcha(token) {
8+
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
9+
method: 'POST',
10+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
11+
body: `secret=${RECAPTCHA_SECRET_KEY}&response=${token}`,
12+
});
13+
14+
return res.json();
15+
}
16+
17+
module.exports = { verifyRecaptcha };

backend/routes/analyze-image.js

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,54 @@ const multer = require('multer');
33
const fs = require('fs');
44
const fetch = require('node-fetch');
55
const vision = require('@google-cloud/vision');
6-
const router = express.Router();
6+
const { verifyRecaptcha } = require('../helpers/verifyRecaptcha');
77

8+
const router = express.Router();
89
const upload = multer({ dest: 'uploads/' });
910

1011
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
1112
const RECAPTCHA_SECRET_KEY = process.env.RECAPTCHA_SECRET_KEY;
1213

13-
if (!GEMINI_API_KEY) throw new Error('Gemini API key is required');
14-
if (!RECAPTCHA_SECRET_KEY) throw new Error('reCAPTCHA secret key is required');
14+
if (!GEMINI_API_KEY) throw new Error('Gemini API key is required.');
15+
if (!RECAPTCHA_SECRET_KEY) throw new Error('reCAPTCHA secret key is required.');
1516

1617
const visionClient = new vision.ImageAnnotatorClient();
1718

18-
async function verifyRecaptcha(token) {
19-
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
20-
method: 'POST',
21-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
22-
body: `secret=${RECAPTCHA_SECRET_KEY}&response=${token}`,
23-
});
24-
return res.json();
25-
}
26-
2719
router.post('/', upload.single('image'), async (req, res) => {
2820
const prompt = req.body.prompt || 'Describe this image';
2921
const cleanPrompt = prompt.trim().replace(/[^a-zA-Z0-9 ?.,!"()\-]/g, '');
3022
const recaptchaToken = req.body.recaptchaToken;
3123

3224
if (!recaptchaToken) {
33-
return res.status(400).json({ error: 'Missing reCAPTCHA token' });
25+
return res.status(400).json({ error: 'Missing reCAPTCHA token.' });
26+
}
27+
28+
if (!req.file) {
29+
return res.status(400).json({ error: 'Image file is required.' });
3430
}
3531

32+
if (!cleanPrompt || cleanPrompt.length > 300) {
33+
return res.status(400).json({ error: 'Prompt must be 1–300 characters.' });
34+
}
35+
36+
// ✅ reCAPTCHA verification
3637
try {
3738
const recaptchaResult = await verifyRecaptcha(recaptchaToken);
38-
if (!recaptchaResult.success || recaptchaResult.score < 0.5) {
39-
console.warn('⚠️ reCAPTCHA fail', {
39+
40+
const score = recaptchaResult.score ?? 0;
41+
const success = recaptchaResult.success === true;
42+
43+
if (!success || score < 0.5) {
44+
console.warn('⚠️ reCAPTCHA verification failed', {
4045
ip: req.ip,
41-
score: recaptchaResult.score,
46+
score,
47+
success,
4248
});
43-
return res.status(403).json({ error: 'reCAPTCHA verification failed' });
49+
return res.status(403).json({ error: 'reCAPTCHA verification failed.' });
4450
}
4551
} catch (err) {
4652
console.error('Error verifying reCAPTCHA:', err);
47-
return res.status(500).json({ error: 'Failed to verify reCAPTCHA' });
48-
}
49-
50-
if (!req.file) {
51-
return res.status(400).json({ error: 'Image file is required' });
52-
}
53-
54-
if (!cleanPrompt || cleanPrompt.length > 300) {
55-
return res.status(400).json({ error: 'Prompt must be 1–300 characters' });
53+
return res.status(500).json({ error: 'Failed to verify reCAPTCHA.' });
5654
}
5755

5856
const imagePath = req.file.path;
@@ -61,6 +59,7 @@ router.post('/', upload.single('image'), async (req, res) => {
6159
// NSFW filtering using Cloud Vision SafeSearch
6260
const [result] = await visionClient.safeSearchDetection(imagePath);
6361
const safe = result.safeSearchAnnotation;
62+
6463
if (
6564
safe.adult === 'LIKELY' ||
6665
safe.adult === 'VERY_LIKELY' ||
@@ -69,9 +68,7 @@ router.post('/', upload.single('image'), async (req, res) => {
6968
safe.racy === 'VERY_LIKELY'
7069
) {
7170
console.warn('Blocked NSFW image:', safe);
72-
return res
73-
.status(403)
74-
.json({ error: 'Image flagged as unsafe by content filter.' });
71+
return res.status(403).json({ error: 'Image flagged as unsafe by content filter.' });
7572
}
7673

7774
const imageBuffer = fs.readFileSync(imagePath);
@@ -105,26 +102,25 @@ router.post('/', upload.single('image'), async (req, res) => {
105102
if (!geminiRes.ok) {
106103
const errorData = await geminiRes.json();
107104
console.error('Gemini API error:', errorData);
108-
return res
109-
.status(geminiRes.status)
110-
.json({ error: errorData.error || 'Unknown error from Gemini API' });
105+
return res.status(geminiRes.status).json({
106+
error: errorData.error?.message || 'Unknown error from Gemini API.',
107+
});
111108
}
112109

113110
const data = await geminiRes.json();
114111
console.log('Gemini API response:', JSON.stringify(data, null, 2));
115112

116113
const responseText = data.candidates?.length
117-
? data.candidates[0].content?.parts?.[0]?.text ||
118-
'Response format unexpected'
119-
: 'No candidates returned from Gemini';
114+
? data.candidates[0].content?.parts?.[0]?.text || 'Response format unexpected.'
115+
: 'No candidates returned from Gemini.';
120116

121117
res.json({ response: responseText });
122118
} catch (err) {
123-
console.error(err);
124-
res.status(500).json({ error: 'Error analyzing image' });
119+
console.error('Error analyzing image:', err);
120+
res.status(500).json({ error: 'Error analyzing image.' });
125121
} finally {
126122
if (fs.existsSync(imagePath)) {
127-
fs.unlinkSync(imagePath); // cleanup
123+
fs.unlinkSync(imagePath); // Cleanup temp file
128124
}
129125
}
130126
});

backend/routes/analyze-text.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,54 @@
11
const express = require('express');
22
const { Storage } = require('@google-cloud/storage');
33
const { GoogleGenerativeAI } = require('@google/generative-ai');
4+
const { verifyRecaptcha } = require('../helpers/verifyRecaptcha');
45

56
const router = express.Router();
67
const storage = new Storage();
78
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
89

9-
// Change this to your actual bucket name
1010
const bucketName = 'upload-center-bucket';
1111

1212
router.post('/', async (req, res) => {
1313
const { gcsUrl } = req.body;
14+
const recaptchaToken = req.headers['x-recaptcha-token'];
1415

1516
if (!gcsUrl) {
16-
return res.status(400).json({ error: 'Missing GCS URL' });
17+
return res.status(400).json({ error: 'Missing GCS URL.' });
18+
}
19+
20+
if (!recaptchaToken) {
21+
return res.status(400).json({ error: 'Missing reCAPTCHA token.' });
22+
}
23+
24+
// ✅ reCAPTCHA verification
25+
try {
26+
const recaptchaResult = await verifyRecaptcha(recaptchaToken);
27+
28+
const score = recaptchaResult.score ?? 0;
29+
const success = recaptchaResult.success === true;
30+
31+
if (!success || score < 0.5) {
32+
console.warn('⚠️ reCAPTCHA verification failed', {
33+
ip: req.ip,
34+
score,
35+
success,
36+
});
37+
return res.status(403).json({ error: 'reCAPTCHA verification failed.' });
38+
}
39+
} catch (err) {
40+
console.error('Error verifying reCAPTCHA:', err);
41+
return res.status(500).json({ error: 'Failed to verify reCAPTCHA.' });
1742
}
1843

1944
try {
20-
// Extract file path from URL
45+
// Extract file contents
2146
const filename = decodeURIComponent(gcsUrl.split('/').pop());
2247
const file = storage.bucket(bucketName).file(`uploads/text-files/${filename}`);
2348
const [contents] = await file.download();
2449
const text = contents.toString('utf8');
2550

26-
// Use Gemini 1.5 to analyze the text
51+
// Analyze with Gemini
2752
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-pro-latest' });
2853

2954
const result = await model.generateContent({
@@ -35,7 +60,7 @@ router.post('/', async (req, res) => {
3560
res.json({ result: responseText });
3661
} catch (err) {
3762
console.error('Vertex AI analysis failed:', err);
38-
res.status(500).json({ error: 'Vertex AI analysis failed' });
63+
res.status(500).json({ error: 'Vertex AI analysis failed.' });
3964
}
4065
});
4166

backend/routes/sentiment.js

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
const express = require('express');
22
const axios = require('axios');
33
const { GoogleGenerativeAI } = require('@google/generative-ai');
4+
const { verifyRecaptcha } = require('../helpers/verifyRecaptcha');
45

56
const router = express.Router();
67
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
78

89
router.post('/', async (req, res) => {
9-
console.log(
10-
`[SENTIMENT] IP: ${req.ip}, UA: ${req.get(
11-
'user-agent'
12-
)}, Time: ${new Date().toISOString()}`
13-
);
10+
console.log(`[SENTIMENT] IP: ${req.ip}, UA: ${req.get('user-agent')}, Time: ${new Date().toISOString()}`);
1411

1512
const { text } = req.body;
1613
const recaptchaToken = req.headers['x-recaptcha-token'];
@@ -20,38 +17,39 @@ router.post('/', async (req, res) => {
2017
}
2118

2219
if (!recaptchaToken) {
23-
return res.status(400).json({ error: 'Missing reCAPTCHA token' });
20+
return res.status(400).json({ error: 'Missing reCAPTCHA token.' });
2421
}
2522

23+
// ✅ reCAPTCHA verification
2624
try {
27-
// ✅ Verify reCAPTCHA v3
28-
const verifyRes = await axios.post(
29-
`https://www.google.com/recaptcha/api/siteverify`,
30-
null,
31-
{
32-
params: {
33-
secret: process.env.RECAPTCHA_SECRET_KEY,
34-
response: recaptchaToken,
35-
},
36-
}
37-
);
38-
39-
const { success, score } = verifyRes.data;
40-
console.log(`[reCAPTCHA] Score: ${score}, Success: ${success}`);
25+
const recaptchaResult = await verifyRecaptcha(recaptchaToken);
26+
27+
const score = recaptchaResult.score ?? 0;
28+
const success = recaptchaResult.success === true;
4129

4230
if (!success || score < 0.5) {
43-
return res.status(403).json({ error: 'Failed reCAPTCHA verification' });
31+
console.warn('⚠️ reCAPTCHA verification failed', {
32+
ip: req.ip,
33+
score,
34+
success,
35+
});
36+
return res.status(403).json({ error: 'reCAPTCHA verification failed.' });
4437
}
38+
} catch (err) {
39+
console.error('Error verifying reCAPTCHA:', err);
40+
return res.status(500).json({ error: 'Failed to verify reCAPTCHA.' });
41+
}
4542

46-
// ✅ Analyze with Gemini
43+
// ✅ Analyze with Gemini
44+
try {
4745
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-pro-latest' });
4846

4947
const prompt = `
50-
Analyze the sentiment of the following text and return only this JSON format:
51-
{ "sentiment": "positive", "score": 0.92 }
48+
Analyze the sentiment of the following text and return only this JSON format:
49+
{ "sentiment": "positive", "score": 0.92 }
5250
53-
Text: "${text}"
54-
`;
51+
Text: "${text}"
52+
`;
5553

5654
const result = await model.generateContent({
5755
contents: [{ role: 'user', parts: [{ text: prompt }] }],
@@ -64,8 +62,9 @@ Text: "${text}"
6462
res.json({ sentiment: parsed });
6563
} catch (err) {
6664
console.error('Gemini Sentiment Error:', err);
65+
6766
if (!res.headersSent) {
68-
res.status(500).json({ error: 'Sentiment analysis failed' });
67+
res.status(500).json({ error: 'Sentiment analysis failed.' });
6968
}
7069
}
7170
});

backend/routes/upload-json-bigquery.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const express = require('express');
33
const { BigQuery } = require('@google-cloud/bigquery');
44
const { Storage } = require('@google-cloud/storage');
55
const rateLimit = require('express-rate-limit');
6+
const { verifyRecaptcha } = require('../helpers/verifyRecaptcha');
67

78
const router = express.Router();
89
const bigquery = new BigQuery();
@@ -23,13 +24,38 @@ const bigQueryLimiter = rateLimit({
2324

2425
router.post('/', bigQueryLimiter, async (req, res) => {
2526
const { gcsUrl } = req.body;
27+
const recaptchaToken = req.headers['x-recaptcha-token'];
2628

2729
if (!gcsUrl) {
2830
return res.status(400).json({ error: 'Missing GCS URL' });
2931
}
3032

33+
if (!recaptchaToken) {
34+
return res.status(400).json({ error: 'Missing reCAPTCHA token.' });
35+
}
36+
3137
console.log(`BigQuery load attempt from IP: ${req.ip}`);
3238

39+
// ✅ reCAPTCHA verification
40+
try {
41+
const recaptchaResult = await verifyRecaptcha(recaptchaToken);
42+
43+
const score = recaptchaResult.score ?? 0;
44+
const success = recaptchaResult.success === true;
45+
46+
if (!success || score < 0.5) {
47+
console.warn('⚠️ reCAPTCHA verification failed', {
48+
ip: req.ip,
49+
score,
50+
success,
51+
});
52+
return res.status(403).json({ error: 'reCAPTCHA verification failed.' });
53+
}
54+
} catch (err) {
55+
console.error('Error verifying reCAPTCHA:', err);
56+
return res.status(500).json({ error: 'Failed to verify reCAPTCHA.' });
57+
}
58+
3359
try {
3460
const filename = decodeURIComponent(gcsUrl.split('/').pop());
3561
const file = storage.bucket(BUCKET_NAME).file(`uploads/json/${filename}`);

0 commit comments

Comments
 (0)