-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.mjs
executable file
·555 lines (450 loc) · 19.4 KB
/
server.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
//! === Module Imports === //
import dotenv from "dotenv"; //EXP: Load environment variables from .env file
dotenv.config();
import express from "express"; //EXP: Import Express web framework
import fetch from "node-fetch"; //EXP: Import node-fetch for making HTTP requests
import path from "path"; //EXP: Import path module for handling file paths
import mysql from "mysql2"; //EXP: Import MySQL module for database interaction
import { fileURLToPath } from "url"; //EXP: Import utility function for working with file URLs
import multer from 'multer';
// import rateLimit from 'express-rate-limit';
//! === Create an instance of the Express application === //
const app = express();
//! === Middleware === //
app.use(express.json()); //EXP: Middleware to parse JSON in request bodies
//! === Port Configuration === //
const port = process.env.PORT || 4000;
//! === Start Server === //
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
}); //EXP: Start the server and listen for requests on the defined port
//! === Directory Configuration === //
const __dirname = path.dirname(fileURLToPath(import.meta.url));
//! === Middleware Configuration === //
app.use(express.urlencoded({ extended: true })); //EXP: Middleware to parse URL-encoded data in request bodies
app.use(express.static("public")); //EXP: Middleware to serve static files from the "public" directory
//! === MySQL Connection Pool Configuration === //
const pool = mysql.createPool({ //EXP: Create a pool of connections to MySQL database
// pool of connections that can be reused //promise will allow to use promise api version of mysql instead of call back version
host: process.env.HOST,
user: process.env.DBUSER,
password: process.env.DBPASS,
database: process.env.DB
}).promise();
//! === Establish Connection and Handle Result === //
pool.getConnection() //EXP: Acquire a connection from the pool
.then(connection => {
console.log('MySQL Database Connected Successfully!');
connection.release(); //EXP: Release the connection back to pool for reuse
})
.catch(error => {
console.error('Error connecting to MySQL Database:', error.message);
});
//! === Rate Limiter For IP === /
// const limiter = rateLimit({
// windowMs: 15 * 60 * 1000, // 15 minutes
// max: 100, //EXP: Limit each IP to 100 requests per windowMs
// });
// app.use(limiter); // Apply the rate limiter to all requests
//! TEST 001 Insert Data
// async function insertData() {
// try {
// const [result] = await pool.query(
// "INSERT INTO user_table (fname, lname, email, password, phone) VALUES (?, ?, ?, ?, ?)",
// ['John', 'Doe', 'john@example.com', 'hashed_password', '1234567890']
// );
// console.log('Data inserted successfully:', result);
// } catch (error) {
// console.error('Error inserting data:', error);
// }
// }
// insertData();
// TEST Select Data
// const [result] = await pool.query("SELECT * FROM user_table") //cause imusing modules i used await on top level await (await is out of async) // [result] to return only 1 array
// console.log(result);
//! === Captcha Verification === //
async function verify_captcha(token) { //EXP: Verify the user's captcha token
const params = new URLSearchParams({
secret: captchaSecretKey,
response: token,
});
try {
const response = await fetch("https://hcaptcha.com/siteverify", {
method: "POST", //EXP: Send a POST request to hCaptcha's verification endpoint
body: params,
});
const data = await response.json();
return [data, null]; //EXP: Parse and return the response data
} catch (error) {
console.error(error);
return [null, error.message];
}
}
//! === sign-up.html === CAPTCHA Verification and Form Submission Route === //
const captchaSecretKey = process.env.hCaptchaSecret; //EXP: Initialize hCaptcha with your hCaptcha secret key
app.post('/verify-captcha', async (req, res) => { //EXP: POST route for verifying captcha and handling form submission
console.log('Received data:', req.body);
//EXP: Extract form data and captcha response from request body
const { 'signup-fname': fname, 'signup-lname': lname, 'sign-up-phone': phone, 'signup-email': email, 'signup-password': password, 'h-captcha-response': captchaResponse } = req.body;
try {
//EXP: Verify CAPTCHA response using your async function
const [captchaData, captchaError] = await verify_captcha(captchaResponse);
if (captchaError) { //EXP: Handle CAPTCHA verification error
return res.status(500).json({ error: 'CAPTCHA verification error' });
}
if (!captchaData.success) {
return res.status(400).json({ error: 'CAPTCHA verification failed' });
}
//EXP: Check if email already exists in the database
const emailExistsQuery = 'SELECT COUNT(*) AS count FROM user_table WHERE email = ?';
const [emailExistsResult] = await pool.query(emailExistsQuery, [email]);
if (emailExistsResult[0].count > 0) {
return res.status(400).json({ error: 'Email already exists' });
}
//EXP: CAPTCHA verification successful, proceed with form submission
const sql = "INSERT INTO user_table (fname, lname, email, password, phone) VALUES (?, ?, ?, ?, ?)";
try {
//EXP: Check for undefined values and replace with null if necessary
const values = [fname, lname, email, password, phone || null];
//EXP: Perform database insertion logic here
const [result] = await pool.query(sql, values);
console.log('Data inserted successfully');
//EXP: Send a success JSON response
res.status(200).json({ message: 'Signup successful' });
} catch (error) {
console.error('Error inserting data:', error);
res.status(500).json({ error: 'Error signing up' });
}
} catch (error) {
console.error('CAPTCHA verification error:', error);
res.status(500).json({ error: 'CAPTCHA verification error' });
}
});
//! === LOGIN === //
app.post('/login', async (req, res) => { //EXP: POST route for handling user login
const { 'login-email': email, 'login-password': password } = req.body;
//EXP: Check email and password against the database
const loginQuery = 'SELECT id FROM user_table WHERE email = ? AND password = ?';
const [loginResult] = await pool.query(loginQuery, [email, password]);
//EXP: Check if login was successful
if (loginResult.length > 0) {
const userId = loginResult[0].id; // Get user ID from the query result
// Successful login
res.status(200).json({ message: 'Login successful', userId: userId }); // Include userId in the response
} else {
// Failed login
res.status(401).json({ error: 'Invalid credentials' });
}
});
//! === Fetch User Info by User ID === //
app.get('/user-info', async (req, res) => {
const userId = req.query.id;
console.log('Received User ID:', userId);
if (!userId) { //EXP: Check if user ID is provided
return res.status(400).json({ error: 'User ID not provided' });
}
try {
//EXP: Execute a SELECT query to retrieve user info based on user ID
const [rows] = await pool.query('SELECT * FROM user_table WHERE id = ?', [userId]);
if (rows.length > 0) { //EXP: Check if user info is found based on the query result
const userInfo = rows[0];
res.status(200).json(userInfo); //EXP: Send user info as JSON
} else {
res.status(404).json({ error: 'User not found' });
}
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
//! === Update User Profile === //
app.put('/update-profile', async (req, res) => {
const userId = req.body.id;
const firstName = req.body.fname;
const lastName = req.body.lname;
const phone = req.body.phone;
const email = req.body.email;
try {
let query = 'UPDATE user_table SET'; //EXP: Construct the SQL query dynamically based on the provided data
const values = [];
if (firstName !== '') {
query += ' fname=?,';
values.push(firstName);
}
if (lastName !== '') {
query += ' lname=?,';
values.push(lastName);
}
if (phone !== '') {
query += ' phone=?,';
values.push(phone);
}
if (email !== '') {
query += ' email=?,';
values.push(email);
}
// Remove the trailing comma and add the WHERE clause
query = query.slice(0, -1) + ' WHERE id=?';
values.push(userId);
//EXP: Execute the dynamic UPDATE query
const [result] = await pool.query(query, values);
if (result.affectedRows > 0) { //EXP: Check if the profile update was successful
res.status(200).json({ message: 'Profile updated successfully' });
} else {
res.status(500).json({ error: 'Profile update failed' });
}
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
//! === Delete User Account === //
app.delete('/delete-account', async (req, res) => { //EXP: Delete user account based on provided user ID
const userId = req.query.id;
if (!userId) { //EXP: Check if user ID is provided
return res.status(400).json({ error: 'User ID not provided' });
}
try {
// Delete the user's account from the database based on the provided user ID
const deleteQuery = 'DELETE FROM user_table WHERE id = ?';
const [result] = await pool.query(deleteQuery, [userId]);
if (result.affectedRows > 0) {
// Account deletion successful
res.status(200).json({ message: 'Account deleted successfully' });
} else {
// No rows were affected, likely due to non-existent user ID
res.status(404).json({ error: 'User not found' });
}
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
//! === Change User Password === //
app.post('/change-password', async (req, res) => { //EXP: Change user password based on provided user ID, old password, and new password
const { userId, oldPassword, newPassword } = req.body;
try {
// Check if the provided old password matches the stored password for the user
const checkPasswordQuery = 'SELECT COUNT(*) AS count FROM user_table WHERE id = ? AND password = ?';
const [passwordCheckResult] = await pool.query(checkPasswordQuery, [userId, oldPassword]);
if (passwordCheckResult[0].count === 0) {
return res.status(401).json({ error: 'Invalid old password' });
}
// Update the user's password with the new one
const updatePasswordQuery = 'UPDATE user_table SET password = ? WHERE id = ?';
await pool.query(updatePasswordQuery, [newPassword, userId]);
res.status(200).json({ message: 'Password changed successfully' });
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
//! === Send User Location Data long lat (user.html)========================================================================
app.post('/save-location', async (req, res) => {
try {
const { userId, latitude, longitude } = req.body;
// Insert the location data into the user's row in the database
const insertQuery = 'UPDATE user_table SET lat = ?, lon = ? WHERE id = ?';
await pool.execute(insertQuery, [latitude, longitude, userId]);
res.status(200).json({ message: 'Location data saved successfully' });
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Failed to save location data' });
}
});
//! === Contact Us === //
app.post("/submit-form", async (req, res) => {
try {
const { fname, lname, email, message } = req.body;
// Insert the form data into the "contact_us" table
const insertQuery = 'INSERT INTO contact_us (first_name, last_name, email, message) VALUES (?, ?, ?, ?)';
const [results, fields] = await pool.execute(insertQuery, [fname, lname, email, message]);
res.status(200).json({ message: 'Form data submitted successfully' });
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Failed to submit form data' });
}
});
//! === Post A Pet For Adoption === //
//EXP: Set up storage for multer
const storage = multer.diskStorage({
destination: './public/resources/images/pets-for-adoption',
filename: (req, file, cb) => {
cb(null, `${Date.now()}_${file.originalname}`);
}
});
const upload = multer({ storage });
//! Handle image uploads
app.post('/uploadImage', upload.single('petPhoto'), (req, res) => {
// Here you can process the uploaded image and get its URL
const imageUrl = `/resources/images/pets-for-adoption/${req.file.filename}`;
// Respond with the image URL
res.json({ imageUrl });
});
//! Handle form submission
app.post('/submitPet', async (req, res) => {
const { userId, petInfo, photoUrl } = req.body; // Assuming you're sending the image URL from the client
try {
const connection = await pool.getConnection();
// Insert the pet information into the database along with the user ID and photo URL
await connection.query(
'INSERT INTO post_for_adoption (photo_url, name, type, breed, color, age_range, size, sex, vaccinated, medical_condition, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[photoUrl, petInfo.petName, petInfo.petType, petInfo.petBreed, petInfo.petColor, petInfo.ageRange, petInfo.petSize, petInfo.petSex, petInfo.vaccinated, petInfo.medicalcondition, userId]
);
connection.release();
// Respond to the client
res.send('Pet information submitted successfully.');
} catch (error) {
console.error('Error:', error);
res.status(500).send('Error submitting pet information.');
}
});
//!=== Pets For Adoption Page === /
// Define a route to fetch pet data one at a time
app.get('/api/getPetData', async (req, res) => {
try {
const connection = await pool.getConnection();
// Query to fetch pet data from the post_for_adoption table
const query = 'SELECT * FROM post_for_adoption';
const [petData] = await connection.query(query);
connection.release();
// Send the fetched data as JSON response
res.json(petData);
} catch (error) {
console.error('Error fetching pet data:', error);
res.status(500).json({ error: 'Failed to fetch pet data' });
}
});
//! === Singleitem === //
app.get('/api/getPetDetails', async (req, res) => {
const petId = req.query.id;
if (!petId) {
return res.status(400).json({ error: 'Missing pet ID' });
}
try {
const connection = await pool.getConnection();
const [rows] = await connection.query('SELECT * FROM post_for_adoption WHERE id = ?', [petId]);
connection.release();
if (rows.length === 0) {
return res.status(404).json({ error: 'Pet not found' });
}
const petDetails = rows[0];
res.json(petDetails);
} catch (error) {
console.error('Error fetching pet details:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
//! === GET CONTACT INFO == //
app.get('/api/getOwnerInfo', async (req, res) => {
const userId = req.query.userId;
if (!userId) {
return res.status(400).json({ error: 'Missing user ID' });
}
try {
const connection = await pool.getConnection();
const [rows] = await connection.query('SELECT fname, lname, phone, lat,lon FROM user_table WHERE id = ?', [userId]);
connection.release();
if (rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
const ownerInfo = rows[0]; 2
console.log('Owner Info:', ownerInfo); // Log owner information
// Use the Geoapify API to get address
const response = await fetch(`https://api.geoapify.com/v1/geocode/reverse?lat=${ownerInfo.lat}&lon=${ownerInfo.lon}&apiKey=3b1a78a94f3142a0b5785bd428e4a91d`);
const geoapifyData = await response.json();
if (response.ok) {
ownerInfo.address = geoapifyData.features[0].properties.formatted;
}
res.json(ownerInfo);
} catch (error) {
console.error('Error fetching owner information:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// //! === Report A Lost Pet === //
// //EXP: Set up storage for multer
// const storageone = multer.diskStorage({
// destination: './public/resources/images/lost-pets',
// filename: (req, file, cb) => {
// cb(null, `${Date.now()}_${file.originalname}`);
// }
// });
// const uploadone = multer({ storageone });
//! Handle image uploads
app.post('/uploadLostPetImage', upload.single('lostPetPhoto'), (req, res) => {
// Here you can process the uploaded image and get its URL
const imageUrl = `/resources/images/pets-for-adoption/${req.file.filename}`;
// Respond with the image URL
res.json({ imageUrl });
});
//! Handle form submission
app.post('/submitLostPet', async (req, res) => {
const { userId, lostPetInfo, photoUrl, latitude, longitude } = req.body;
try {
console.log('Received submission request:', req.body); // Logging the received data
// Add data validation to ensure required fields are present
if (!userId || !lostPetInfo || !photoUrl || !latitude || !longitude) {
return res.status(400).json({ error: 'Invalid request data.' });
}
const lostPet = {
photoUrl,
petType: lostPetInfo.petType,
petBreed: lostPetInfo.petBreed,
petColor: lostPetInfo.petColor,
ageRange: lostPetInfo.ageRange,
petSize: lostPetInfo.petSize,
petSex: lostPetInfo.petSex,
latitude,
longitude,
};
const connection = await pool.getConnection();
// Use parameterized queries to prevent SQL injection
await connection.execute(
'INSERT INTO lost_pet (lost_pet_photo_url, lost_pet_type, lost_pet_breed, lost_pet_color, lost_pet_approximate_age, lost_pet_size, lost_pet_sex, user_id, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[lostPet.photoUrl, lostPet.petType, lostPet.petBreed, lostPet.petColor, lostPet.ageRange, lostPet.petSize, lostPet.petSex, userId, lostPet.latitude, lostPet.longitude]
);
connection.release();
// Send a JSON response indicating success
res.status(201).json({ message: 'Lost Pet information submitted successfully.', lostPet });
} catch (error) {
console.error('Error:', error);
// Handle specific errors, e.g., duplicate entries
if (error.code === 'ER_DUP_ENTRY') {
res.status(400).json({ error: 'Duplicate entry. This pet may have already been submitted.' });
} else {
res.status(500).json({ error: 'Error submitting Lost pet information.' });
}
}
});
//! === Marker Data
app.get('/getMarkersData', async (req, res) => {
try {
const connection = await pool.getConnection();
const query = `
SELECT lp.lost_pet_photo_url, lp.lost_pet_type, lp.time_found, u.phone, lp.latitude, lp.longitude
FROM lost_pet lp
JOIN user_table u ON lp.user_id = u.id
`;
const [rows] = await connection.query(query);
connection.release();
console.log(rows[0]);
const markersData = rows.map(row => ({
lost_pet_photo_url: row.lost_pet_photo_url,
lost_pet_type: row.lost_pet_type,
time_found: row.time_found,
contactNumber: row.phone,
latitude: row.latitude,
longitude: row.longitude,
}));
res.json(markersData);
} catch (error) {
console.error('Error fetching data:', error);
res.status(500).send('Error fetching data');
}
});
//! === Handle 404 Not Found === //
app.use((req, res) => { //EXP: Handle requests that do not match any defined routes with a 404 error page
return res.status(404).sendFile(path.join(__dirname, "public", "404.html")); // at the end cause it works from top to buttom
});
//?search bar functionality