Spaces:
Sleeping
Sleeping
Commit
·
4fe453b
1
Parent(s):
1dffd8b
Add backend files for deployment
Browse files- app.py +408 -0
- requirements.txt +0 -0
app.py
ADDED
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask, request, jsonify, render_template, url_for
|
2 |
+
from flask_cors import CORS
|
3 |
+
import torch
|
4 |
+
import torch.nn as nn
|
5 |
+
from torchvision import models, transforms
|
6 |
+
from PIL import Image
|
7 |
+
from huggingface_hub import hf_hub_download
|
8 |
+
import os
|
9 |
+
from mtcnn import MTCNN
|
10 |
+
import cv2
|
11 |
+
from flask_bcrypt import generate_password_hash, check_password_hash
|
12 |
+
from pymongo import MongoClient
|
13 |
+
import numpy as np
|
14 |
+
from werkzeug.security import generate_password_hash, check_password_hash
|
15 |
+
from werkzeug.utils import secure_filename
|
16 |
+
import logging
|
17 |
+
import matplotlib.pyplot as plt
|
18 |
+
import seaborn as sns
|
19 |
+
from transformers import AutoImageProcessor, AutoModelForImageClassification # New imports
|
20 |
+
|
21 |
+
# Setup logging
|
22 |
+
logging.basicConfig(level=logging.INFO)
|
23 |
+
|
24 |
+
app = Flask(__name__, template_folder="templates", static_folder="static")
|
25 |
+
CORS(app)
|
26 |
+
|
27 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
28 |
+
UPLOAD_FOLDER = "static/uploads"
|
29 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
30 |
+
|
31 |
+
# ------------------- Model Loading Functions -------------------
|
32 |
+
|
33 |
+
def load_model_from_hf(repo_id, filename, num_classes):
|
34 |
+
model_path = hf_hub_download(repo_id=repo_id, filename=filename)
|
35 |
+
model = models.convnext_tiny(weights=None)
|
36 |
+
in_features = model.classifier[2].in_features
|
37 |
+
model.classifier[2] = nn.Linear(in_features, num_classes)
|
38 |
+
model.load_state_dict(torch.load(model_path, map_location=device))
|
39 |
+
model.to(device)
|
40 |
+
model.eval()
|
41 |
+
return model
|
42 |
+
|
43 |
+
# Load the existing deepfake/cheapfake models
|
44 |
+
deepfake_model = load_model_from_hf("faryalnimra/DFDC-detection-model", "DFDC.pth", 2)
|
45 |
+
cheapfake_model = load_model_from_hf("faryalnimra/ORIG-TAMP", "ORIG-TAMP.pth", 1)
|
46 |
+
|
47 |
+
# ------------------- New Real/Fake Detector Model -------------------
|
48 |
+
# This model determines if the uploaded image is real (label 1) or fake (label 0)
|
49 |
+
model_name = "prithivMLmods/Deep-Fake-Detector-Model"
|
50 |
+
processor = AutoImageProcessor.from_pretrained(model_name, use_fast=False)
|
51 |
+
realfake_detector = AutoModelForImageClassification.from_pretrained(model_name)
|
52 |
+
realfake_detector.to(device)
|
53 |
+
realfake_detector.eval()
|
54 |
+
|
55 |
+
# ------------------- Image Preprocessing -------------------
|
56 |
+
|
57 |
+
transform = transforms.Compose([
|
58 |
+
transforms.ToTensor(),
|
59 |
+
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
60 |
+
])
|
61 |
+
|
62 |
+
# ------------------- Face Detector -------------------
|
63 |
+
|
64 |
+
face_detector = MTCNN()
|
65 |
+
|
66 |
+
def detect_face(image_path):
|
67 |
+
image = cv2.imread(image_path)
|
68 |
+
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
69 |
+
faces = face_detector.detect_faces(image_rgb)
|
70 |
+
face_count = sum(1 for face in faces if face.get("confidence", 0) > 0.90 and face.get("box", [0, 0, 0, 0])[2] > 30)
|
71 |
+
return face_count
|
72 |
+
|
73 |
+
# ------------------- API Endpoint: /predict -------------------
|
74 |
+
@app.route("/predict", methods=["POST"])
|
75 |
+
def predict():
|
76 |
+
if "file" not in request.files:
|
77 |
+
return jsonify({"error": "No file uploaded"}), 400
|
78 |
+
|
79 |
+
file = request.files["file"]
|
80 |
+
prediction_type = request.form.get("prediction_type", "real_vs_fake") # default
|
81 |
+
|
82 |
+
filename = os.path.join(UPLOAD_FOLDER, file.filename)
|
83 |
+
file.save(filename)
|
84 |
+
|
85 |
+
try:
|
86 |
+
image = Image.open(filename).convert("RGB")
|
87 |
+
image_tensor = transform(image).unsqueeze(0).to(device)
|
88 |
+
except Exception as e:
|
89 |
+
return jsonify({"error": "Error processing image", "details": str(e)}), 500
|
90 |
+
|
91 |
+
# --------- CASE 1: ONLY Real/Fake Prediction ----------
|
92 |
+
if prediction_type == "real_vs_fake":
|
93 |
+
with torch.no_grad():
|
94 |
+
inputs = processor(images=image, return_tensors="pt").to(device)
|
95 |
+
outputs_realfake = realfake_detector(**inputs)
|
96 |
+
pred_label = torch.argmax(outputs_realfake.logits, dim=1).item()
|
97 |
+
|
98 |
+
if pred_label == 1:
|
99 |
+
return jsonify({
|
100 |
+
"prediction": "Real",
|
101 |
+
"message": "Image is authentic. No further processing.",
|
102 |
+
"image_url": url_for("static", filename=f"uploads/{file.filename}")
|
103 |
+
})
|
104 |
+
else:
|
105 |
+
return jsonify({
|
106 |
+
"prediction": "Fake",
|
107 |
+
"message": "Image is fake, but type (Deepfake/Cheapfake) not determined in this mode.",
|
108 |
+
"image_url": url_for("static", filename=f"uploads/{file.filename}")
|
109 |
+
})
|
110 |
+
|
111 |
+
# --------- CASE 2: Deepfake vs Cheapfake Analysis ----------
|
112 |
+
elif prediction_type == "deepfake_vs_cheapfake":
|
113 |
+
with torch.no_grad():
|
114 |
+
deepfake_probs = torch.softmax(deepfake_model(image_tensor), dim=1)[0]
|
115 |
+
deepfake_confidence_before = deepfake_probs[1].item() * 100
|
116 |
+
cheapfake_confidence_before = torch.sigmoid(cheapfake_model(image_tensor)).item() * 100
|
117 |
+
|
118 |
+
face_count = detect_face(filename)
|
119 |
+
face_factor = min(face_count / 2, 1)
|
120 |
+
|
121 |
+
if deepfake_confidence_before <= cheapfake_confidence_before:
|
122 |
+
adjusted_deepfake_confidence = deepfake_confidence_before * (1 + 0.3 * face_factor)
|
123 |
+
adjusted_cheapfake_confidence = cheapfake_confidence_before * (1 - 0.3 * face_factor)
|
124 |
+
else:
|
125 |
+
adjusted_deepfake_confidence = deepfake_confidence_before
|
126 |
+
adjusted_cheapfake_confidence = cheapfake_confidence_before
|
127 |
+
|
128 |
+
fake_type = "Deepfake" if adjusted_deepfake_confidence > adjusted_cheapfake_confidence else "Cheapfake"
|
129 |
+
|
130 |
+
return jsonify({
|
131 |
+
"prediction": "Fake",
|
132 |
+
"fake_type": fake_type,
|
133 |
+
"deepfake_confidence_before": f"{deepfake_confidence_before:.2f}%",
|
134 |
+
"deepfake_confidence_adjusted": f"{adjusted_deepfake_confidence:.2f}%",
|
135 |
+
"cheapfake_confidence_before": f"{cheapfake_confidence_before:.2f}%",
|
136 |
+
"cheapfake_confidence_adjusted": f"{adjusted_cheapfake_confidence:.2f}%",
|
137 |
+
"faces_detected": face_count,
|
138 |
+
"image_url": url_for("static", filename=f"uploads/{file.filename}")
|
139 |
+
})
|
140 |
+
|
141 |
+
# --------- CASE 3: Invalid prediction_type ---------
|
142 |
+
else:
|
143 |
+
return jsonify({"error": "Invalid prediction_type. Use 'real_vs_fake' or 'deepfake_vs_cheapfake'"}), 400
|
144 |
+
|
145 |
+
# ------------------- Heatmap Generator and API -------------------
|
146 |
+
|
147 |
+
|
148 |
+
|
149 |
+
# Flask setup
|
150 |
+
|
151 |
+
UPLOAD_FOLDER = "static/uploads"
|
152 |
+
HEATMAP_FOLDER = "static/heatmaps"
|
153 |
+
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
|
154 |
+
|
155 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
156 |
+
os.makedirs(HEATMAP_FOLDER, exist_ok=True)
|
157 |
+
|
158 |
+
def allowed_file(filename):
|
159 |
+
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
|
160 |
+
|
161 |
+
# Load your model
|
162 |
+
deepfake_model = load_model_from_hf("faryalnimra/DFDC-detection-model", "DFDC.pth", 2)
|
163 |
+
deepfake_model.eval()
|
164 |
+
|
165 |
+
# Choose the last Conv2D layer
|
166 |
+
target_layer = None
|
167 |
+
for name, module in deepfake_model.named_modules():
|
168 |
+
if isinstance(module, torch.nn.Conv2d):
|
169 |
+
target_layer = module
|
170 |
+
|
171 |
+
# Grad-CAM class
|
172 |
+
class GradCAM:
|
173 |
+
def __init__(self, model, target_layer):
|
174 |
+
self.model = model
|
175 |
+
self.target_layer = target_layer
|
176 |
+
self.gradients = None
|
177 |
+
self.activations = None
|
178 |
+
self._register_hooks()
|
179 |
+
|
180 |
+
def _register_hooks(self):
|
181 |
+
def forward_hook(module, input, output):
|
182 |
+
self.activations = output.detach()
|
183 |
+
|
184 |
+
def backward_hook(module, grad_in, grad_out):
|
185 |
+
self.gradients = grad_out[0].detach()
|
186 |
+
|
187 |
+
self.target_layer.register_forward_hook(forward_hook)
|
188 |
+
self.target_layer.register_backward_hook(backward_hook)
|
189 |
+
|
190 |
+
def generate(self, input_tensor, class_idx=None):
|
191 |
+
self.model.eval()
|
192 |
+
output = self.model(input_tensor)
|
193 |
+
|
194 |
+
if class_idx is None:
|
195 |
+
class_idx = torch.argmax(output, dim=1).item()
|
196 |
+
|
197 |
+
self.model.zero_grad()
|
198 |
+
loss = output[0, class_idx]
|
199 |
+
loss.backward()
|
200 |
+
|
201 |
+
gradients = self.gradients.cpu().numpy()[0]
|
202 |
+
activations = self.activations.cpu().numpy()[0]
|
203 |
+
|
204 |
+
weights = np.mean(gradients, axis=(1, 2))
|
205 |
+
cam = np.zeros(activations.shape[1:], dtype=np.float32)
|
206 |
+
|
207 |
+
for i, w in enumerate(weights):
|
208 |
+
cam += w * activations[i, :, :]
|
209 |
+
|
210 |
+
cam = np.maximum(cam, 0)
|
211 |
+
cam = cv2.resize(cam, (input_tensor.size(3), input_tensor.size(2)))
|
212 |
+
cam = cam - np.min(cam)
|
213 |
+
cam = cam / np.max(cam)
|
214 |
+
return cam, output
|
215 |
+
|
216 |
+
# Preprocessing
|
217 |
+
preprocess = transforms.Compose([
|
218 |
+
transforms.Resize((224, 224)),
|
219 |
+
transforms.ToTensor(),
|
220 |
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
221 |
+
])
|
222 |
+
|
223 |
+
gradcam = GradCAM(deepfake_model, target_layer)
|
224 |
+
|
225 |
+
# Generate heatmap and prediction
|
226 |
+
def generate_heatmap(original_image_path, heatmap_save_path):
|
227 |
+
img = Image.open(original_image_path).convert("RGB")
|
228 |
+
input_tensor = preprocess(img).unsqueeze(0)
|
229 |
+
|
230 |
+
cam, output = gradcam.generate(input_tensor)
|
231 |
+
|
232 |
+
# Get prediction
|
233 |
+
probabilities = torch.nn.functional.softmax(output, dim=1)[0]
|
234 |
+
class_idx = torch.argmax(probabilities).item()
|
235 |
+
confidence = probabilities[class_idx].item()
|
236 |
+
label = "Fake" if class_idx == 1 else "Real"
|
237 |
+
|
238 |
+
# Generate heatmap
|
239 |
+
heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)
|
240 |
+
heatmap = cv2.GaussianBlur(heatmap, (7, 7), 0)
|
241 |
+
heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
|
242 |
+
|
243 |
+
img_np = np.array(img.resize((224, 224)))
|
244 |
+
|
245 |
+
superimposed_img = heatmap * 0.5 + img_np * 0.5
|
246 |
+
superimposed_img = np.uint8(superimposed_img)
|
247 |
+
|
248 |
+
Image.fromarray(superimposed_img).save(heatmap_save_path)
|
249 |
+
|
250 |
+
return label, confidence
|
251 |
+
|
252 |
+
# Flask route
|
253 |
+
@app.route("/generate_heatmap", methods=["POST"])
|
254 |
+
def generate_heatmap_api():
|
255 |
+
if "file" not in request.files:
|
256 |
+
return jsonify({"error": "No file uploaded"}), 400
|
257 |
+
|
258 |
+
file = request.files["file"]
|
259 |
+
|
260 |
+
if file.filename == "" or not allowed_file(file.filename):
|
261 |
+
return jsonify({"error": "Invalid file type. Allowed types are .png, .jpg, .jpeg"}), 400
|
262 |
+
|
263 |
+
filename = secure_filename(file.filename)
|
264 |
+
original_image_path = os.path.join(UPLOAD_FOLDER, filename)
|
265 |
+
|
266 |
+
try:
|
267 |
+
file.save(original_image_path)
|
268 |
+
except Exception as e:
|
269 |
+
return jsonify({"error": "Failed to save the file"}), 500
|
270 |
+
|
271 |
+
heatmap_filename = f"heatmap_{filename}"
|
272 |
+
heatmap_path = os.path.join(HEATMAP_FOLDER, heatmap_filename)
|
273 |
+
|
274 |
+
label, confidence = generate_heatmap(original_image_path, heatmap_path)
|
275 |
+
|
276 |
+
return jsonify({
|
277 |
+
"original_image_url": url_for("static", filename=f"uploads/{filename}", _external=True),
|
278 |
+
"heatmap_image_url": url_for("static", filename=f"heatmaps/{heatmap_filename}", _external=True),
|
279 |
+
"prediction": label,
|
280 |
+
"confidence": f"{confidence:.2f}"
|
281 |
+
})
|
282 |
+
|
283 |
+
# To run:
|
284 |
+
# if __name__ == "__main__":
|
285 |
+
# app.run(debug=True)
|
286 |
+
|
287 |
+
|
288 |
+
|
289 |
+
|
290 |
+
|
291 |
+
|
292 |
+
|
293 |
+
#MongoDB Atlantis from flask import Flask, request, jsonify
|
294 |
+
|
295 |
+
|
296 |
+
# MongoDB connection
|
297 |
+
client = MongoClient('mongodb+srv://fakecatcherai:[email protected]/?retryWrites=true&w=majority&appName=Cluster0')
|
298 |
+
db = client['fakecatcherDB']
|
299 |
+
users_collection = db['users']
|
300 |
+
contacts_collection = db['contacts']
|
301 |
+
|
302 |
+
def is_valid_password(password):
|
303 |
+
if (len(password) < 8 or
|
304 |
+
not re.search(r'[A-Z]', password) or
|
305 |
+
not re.search(r'[a-z]', password) or
|
306 |
+
not re.search(r'[0-9]', password) or
|
307 |
+
not re.search(r'[!@#$%^&*(),.?":{}|<>]', password)):
|
308 |
+
return False
|
309 |
+
return True
|
310 |
+
|
311 |
+
@app.route('/Register', methods=['POST'])
|
312 |
+
def register():
|
313 |
+
data = request.get_json()
|
314 |
+
first_name = data.get('firstName')
|
315 |
+
last_name = data.get('lastName')
|
316 |
+
email = data.get('email')
|
317 |
+
password = data.get('password')
|
318 |
+
|
319 |
+
if users_collection.find_one({'email': email}):
|
320 |
+
logging.warning(f"Attempted register with existing email: {email}")
|
321 |
+
return jsonify({'message': 'Email already exists!'}), 400
|
322 |
+
|
323 |
+
# ✅ Password constraints check
|
324 |
+
if not is_valid_password(password):
|
325 |
+
return jsonify({'message': 'Password must be at least 8 characters long and include uppercase, lowercase, number, and special character.'}), 400
|
326 |
+
|
327 |
+
hashed_pw = generate_password_hash(password)
|
328 |
+
users_collection.insert_one({
|
329 |
+
'first_name': first_name,
|
330 |
+
'last_name': last_name,
|
331 |
+
'email': email,
|
332 |
+
'password': hashed_pw
|
333 |
+
})
|
334 |
+
|
335 |
+
logging.info(f"New user registered: {first_name} {last_name}, Email: {email}")
|
336 |
+
return jsonify({'message': 'Registration successful!'}), 201
|
337 |
+
|
338 |
+
# 🔵 Login Route
|
339 |
+
@app.route('/Login', methods=['POST'])
|
340 |
+
def login():
|
341 |
+
data = request.get_json()
|
342 |
+
email = data.get('email')
|
343 |
+
password = data.get('password')
|
344 |
+
|
345 |
+
# Check if the user exists
|
346 |
+
user = users_collection.find_one({'email': email})
|
347 |
+
if not user or not check_password_hash(user['password'], password):
|
348 |
+
logging.warning(f"Failed login attempt for email: {email}")
|
349 |
+
return jsonify({'message': 'Invalid email or password!'}), 401
|
350 |
+
|
351 |
+
logging.info(f"User logged in successfully: {email}")
|
352 |
+
return jsonify({'message': 'Login successful!'}), 200
|
353 |
+
@app.route('/ForgotPassword', methods=['POST'])
|
354 |
+
def forgot_password():
|
355 |
+
data = request.get_json()
|
356 |
+
email = data.get('email')
|
357 |
+
new_password = data.get('newPassword')
|
358 |
+
confirm_password = data.get('confirmPassword')
|
359 |
+
|
360 |
+
# Check if passwords match
|
361 |
+
if new_password != confirm_password:
|
362 |
+
logging.warning(f"Password reset failed. Passwords do not match for email: {email}")
|
363 |
+
return jsonify({'message': 'Passwords do not match!'}), 400
|
364 |
+
|
365 |
+
# Check if the user exists
|
366 |
+
user = users_collection.find_one({'email': email})
|
367 |
+
if not user:
|
368 |
+
logging.warning(f"Password reset attempt for non-existent email: {email}")
|
369 |
+
return jsonify({'message': 'User not found!'}), 404
|
370 |
+
|
371 |
+
# Hash the new password and update it
|
372 |
+
hashed_pw = generate_password_hash(new_password)
|
373 |
+
users_collection.update_one({'email': email}, {'$set': {'password': hashed_pw}})
|
374 |
+
|
375 |
+
logging.info(f"Password successfully reset for email: {email}")
|
376 |
+
return jsonify({'message': 'Password updated successfully!'}), 200
|
377 |
+
|
378 |
+
|
379 |
+
|
380 |
+
|
381 |
+
|
382 |
+
|
383 |
+
# 🟣 Contact Form Route (React Page: Contact)
|
384 |
+
@app.route('/Contact', methods=['POST'])
|
385 |
+
def contact():
|
386 |
+
data = request.get_json()
|
387 |
+
email = data.get('email')
|
388 |
+
query = data.get('query')
|
389 |
+
message = data.get('message')
|
390 |
+
|
391 |
+
# Check if all fields are provided
|
392 |
+
if not email or not query or not message:
|
393 |
+
logging.warning(f"Incomplete contact form submission from email: {email}")
|
394 |
+
return jsonify({'message': 'All fields are required!'}), 400
|
395 |
+
|
396 |
+
# Insert the contact data
|
397 |
+
contact_data = {
|
398 |
+
'email': email,
|
399 |
+
'query': query,
|
400 |
+
'message': message
|
401 |
+
}
|
402 |
+
contacts_collection.insert_one(contact_data)
|
403 |
+
|
404 |
+
logging.info(f"Contact form submitted successfully from email: {email}")
|
405 |
+
return jsonify({'message': 'Your message has been sent successfully.'}), 200
|
406 |
+
|
407 |
+
if __name__ == '__main__':
|
408 |
+
app.run(debug=True)
|
requirements.txt
ADDED
Binary file (1.07 kB). View file
|
|