gatepass-backend / main.py
Akash4911's picture
Add root route for HF app page
314114c
print("DEBUG: Script started", flush=True)
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
print("DEBUG: CORS imported", flush=True)
import numpy as np
print("DEBUG: numpy imported", flush=True)
import base64
print("DEBUG: base64 imported", flush=True)
import cv2
print("DEBUG: cv2 imported", flush=True)
import io
import os
import json
import requests
print("DEBUG: requests imported", flush=True)
from PIL import Image
print("DEBUG: PIL imported", flush=True)
from datetime import datetime, timezone
import time
import time
# Start of executable logic
print("DEBUG: Executable logic started", flush=True)
import firebase_admin
from firebase_admin import credentials
print("DEBUG: firebase_admin and credentials imported", flush=True)
# Helper for lazy loading storage
def get_firebase_storage():
try:
from firebase_admin import storage
return storage
except Exception as e:
print(f"ERROR: Failed to import firebase_admin.storage: {e}", flush=True)
return None
# Helper for lazy loading firestore
def get_firestore_client():
global db
if db:
return db
try:
from firebase_admin import firestore
db = firestore.client()
print("Firebase Admin SDK and Firestore initialized via lazy load", flush=True)
return db
except Exception as e:
print(f"ERROR: Failed to initialize Firestore via lazy load: {e}", flush=True)
return None
# Initialize Firebase Admin SDK
print("DEBUG: Initializing Firebase credentials...", flush=True)
if not firebase_admin._apps:
print("DEBUG: Initializing firebase_admin app...", flush=True)
storage_options = {'storageBucket': 'gatepass-49d43.firebasestorage.app'}
# 1) Cloud Run: use attached service account (ADC)
if os.getenv("K_SERVICE"):
firebase_app = firebase_admin.initialize_app(options=storage_options)
print("DEBUG: firebase_admin initialized via ADC (Cloud Run)", flush=True)
else:
# 2) Hugging Face/other hosted env: service account JSON in env var
sa_json = os.getenv("FIREBASE_SERVICE_ACCOUNT_JSON")
if sa_json:
cred = credentials.Certificate(json.loads(sa_json))
firebase_app = firebase_admin.initialize_app(cred, storage_options)
print("DEBUG: firebase_admin initialized via FIREBASE_SERVICE_ACCOUNT_JSON", flush=True)
else:
# 3) Local development fallback (file-based key)
cred_path = os.path.join(os.path.dirname(__file__), 'serviceAccountKey.json')
cred = credentials.Certificate(cred_path)
firebase_app = firebase_admin.initialize_app(cred, storage_options)
print(f"DEBUG: firebase_admin initialized via local file: {cred_path}", flush=True)
# Initialize Firestore client
db = None
print("Firestore initialization deferred to lazy loading", flush=True)
def upload_to_firebase_storage(image_data, filename, content_type='image/jpeg'):
"""Upload image data to Firebase Storage and return the download URL."""
storage = get_firebase_storage()
if not storage:
raise Exception("Firebase Storage not available")
bucket = storage.bucket()
blob = bucket.blob(f"face_verification/{filename}")
# Upload the image
blob.upload_from_string(image_data, content_type=content_type)
# Make the blob publicly accessible
blob.make_public()
return blob.public_url
app = Flask(__name__)
# Enable CORS for cross-origin requests from the app
CORS(app)
@app.route('/', methods=['GET'])
def root():
return jsonify({"message": "GatePass backend running"}), 200
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({"status": "ok"}), 200
@app.before_request
def log_request_info():
print(f"Incoming Request: {request.method} {request.path}")
json_data = request.get_json(silent=True)
if json_data:
print(f"Payload keys: {list(json_data.keys())}")
# Configuration
MODEL_PATH = 'blaze_face_short_range.tflite'
MODEL_URL = 'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite'
# NEW: Local storage folder for verified faces
UPLOAD_FOLDER = 'storage'
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
# NEW: Pass storage folder for IoT gate access
# Must match the path in gate_server.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PASS_STORAGE_FOLDER = os.path.join(BASE_DIR, 'app', 'src', 'services', 'storage', 'passes')
if not os.path.exists(PASS_STORAGE_FOLDER):
os.makedirs(PASS_STORAGE_FOLDER, exist_ok=True)
print(f"Pass storage folder: {PASS_STORAGE_FOLDER}", flush=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['PASS_STORAGE_FOLDER'] = PASS_STORAGE_FOLDER
def download_model():
if not os.path.exists(MODEL_PATH):
print(f"Downloading MediaPipe model from {MODEL_URL}...")
try:
response = requests.get(MODEL_URL)
with open(MODEL_PATH, 'wb') as f:
f.write(response.content)
print("Model downloaded successfully.")
except Exception as e:
print(f"Error downloading model: {e}")
print("Warning: Model download failed, running without face detection")
# Try to initialize OpenCV Haar Cascade face detector
CASCADE_PATHS = [
os.path.join(os.path.dirname(__file__), 'haarcascade_frontalface_default.xml'),
'/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml',
'/usr/local/share/opencv4/haarcascades/haarcascade_frontalface_default.xml'
]
face_cascade = None
for path in CASCADE_PATHS:
if os.path.exists(path):
face_cascade = cv2.CascadeClassifier(path)
if not face_cascade.empty():
print(f"OpenCV Haar Cascade face detector loaded: {path}", flush=True)
break
if face_cascade is None or face_cascade.empty():
print("Could not load Haar Cascade face detector. Face detection will not work.", flush=True)
@app.route('/storage/<path:filename>')
def serve_storage(filename):
print(f"Serving file: {filename}", flush=True)
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@app.route('/verify-face', methods=['POST'])
def verify_face():
"""
Face verification endpoint for mobile app biometric authentication.
- Compresses images to under 500KB
- Uploads to Firebase Storage
- Detects faces using OpenCV Haar Cascade
"""
try:
data = request.json
if not data or 'image' not in data:
return jsonify({"error": "No image data provided"}), 400
user_id = data.get('userId', 'unknown_user')
# Decode base64 image
image_data = base64.b64decode(data['image'])
# Open image with PIL for compression
image = Image.open(io.BytesIO(image_data))
rgb_image_np = np.array(image.convert('RGB'))
# Compress image to under 500KB
max_width = 1024
max_height = 1024
max_size_kb = 500
# Resize if needed
img_width, img_height = image.size
if img_width > max_width or img_height > max_height:
image.thumbnail((max_width, max_height))
# Compress and get image bytes
compressed_buffer = io.BytesIO()
image.save(compressed_buffer, format='JPEG', quality=85, optimize=True)
compressed_data = compressed_buffer.getvalue()
# Check size and reduce quality if needed
quality = 85
while len(compressed_data) > max_size_kb * 1024 and quality > 20:
quality -= 10
compressed_buffer = io.BytesIO()
image.save(compressed_buffer, format='JPEG', quality=quality, optimize=True)
compressed_data = compressed_buffer.getvalue()
print(f"📸 Original size: {len(image_data) / 1024:.1f}KB, Compressed: {len(compressed_data) / 1024:.1f}KB (quality: {quality})")
# Generate unique filename
timestamp = int(time.time())
filename = f"{user_id}_{timestamp}.jpg"
# Upload to Firebase Storage
try:
download_url = upload_to_firebase_storage(compressed_data, filename, 'image/jpeg')
print(f"Image uploaded to Firebase: {download_url}", flush=True)
except Exception as fb_error:
print(f"Firebase upload failed: {fb_error}, saving locally", flush=True)
# Fallback to local storage
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
with open(filepath, 'wb') as f:
f.write(compressed_data)
download_url = f"file://{filepath}"
# Perform Face Detection for verification (OpenCV Haar Cascade)
try:
gray = cv2.cvtColor(rgb_image_np, cv2.COLOR_RGB2GRAY)
faces = []
if face_cascade is not None and not face_cascade.empty():
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
if len(faces) > 0:
return jsonify({
"verified": True,
"face_count": len(faces),
"message": "Face detected successfully (OpenCV Haar Cascade)",
"imageUrl": download_url,
"filename": filename,
"compressedSizeKB": round(len(compressed_data) / 1024, 1)
})
else:
return jsonify({
"verified": False,
"message": "No face detected in the image"
}), 200
except Exception as e:
print(f"Face detection error: {e}")
return jsonify({
"verified": False,
"message": f"Face detection error: {str(e)}"
}), 500
except Exception as e:
print(f"Error: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/save-pass', methods=['POST'])
def save_pass():
"""Save pass files (student image + QR code) to shared storage for IoT gate access"""
try:
data = request.json
if not data:
return jsonify({"error": "No data provided"}), 400
pass_id = data.get('pass_id')
person_id = data.get('person_id') or data.get('student_id') or data.get('employee_id') or data.get('userId')
student_image_base64 = data.get('student_image') or data.get('person_image')
qr_image_base64 = data.get('qr_image')
if not pass_id:
return jsonify({"error": "pass_id is required"}), 400
pass_folder = app.config['PASS_STORAGE_FOLDER']
# Save student image
student_image_path = None
if student_image_base64:
try:
student_image_data = base64.b64decode(student_image_base64)
student_image_filename = f"{pass_id}_person.jpg"
student_image_path = os.path.join(pass_folder, student_image_filename)
with open(student_image_path, 'wb') as f:
f.write(student_image_data)
print(f"💾 Person image saved: {student_image_path}")
except Exception as img_err:
print(f"⚠ Error saving student image: {img_err}")
# Save QR code image
qr_image_path = None
if qr_image_base64:
try:
qr_image_data = base64.b64decode(qr_image_base64)
qr_image_filename = f"{pass_id}_qr.png"
qr_image_path = os.path.join(pass_folder, qr_image_filename)
with open(qr_image_path, 'wb') as f:
f.write(qr_image_data)
print(f"💾 QR image saved: {qr_image_path}")
except Exception as qr_err:
print(f"⚠ Error saving QR image: {qr_err}")
# Create/update index.json for IoT gate quick access
index_file = os.path.join(pass_folder, 'index.json')
index_data = {"passes": {}}
if os.path.exists(index_file):
try:
with open(index_file, 'r') as f:
index_data = json.load(f)
except:
pass
index_data['passes'][pass_id] = {
"person_id": person_id,
"person_image": student_image_path,
"qr_image": qr_image_path,
"stored_at": str(os.path.getmtime(pass_folder)) if os.path.exists(pass_folder) else None
}
with open(index_file, 'w') as f:
json.dump(index_data, f, indent=2)
return jsonify({
"success": True,
"pass_id": pass_id,
"student_image": student_image_path,
"qr_image": qr_image_path
})
except Exception as e:
print(f"Error saving pass: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/upload-profile-image', methods=['POST'])
def upload_profile_image():
"""
Upload profile image to Firebase Storage.
This endpoint receives base64 image from React Native app and uploads to Firebase Storage
using firebase-admin SDK (which doesn't have blob/ArrayBuffer issues).
Expected JSON body:
{
"image": "base64_encoded_image_data",
"userId": "user_id_string",
"contentType": "image/jpeg" (optional, defaults to image/jpeg)
}
Returns:
{
"success": true,
"downloadURL": "https://storage.googleapis.com/...",
"storagePath": "profiles/profile_userId_timestamp.jpg",
"filename": "profile_userId_timestamp.jpg",
"userId": "user_id",
"uploadedAt": "2026-02-07T12:48:00Z"
}
"""
try:
print("\n--- 📥 Received request to /upload-profile-image ---")
# Handle native binary upload (from FileSystem.uploadAsync)
if request.content_type == 'image/jpeg':
print("📦 Detected binary upload stream")
image_data = request.data
user_id = request.headers.get('x-user-id', 'unknown_user')
content_type = 'image/jpeg'
if not image_data:
print("❌ Error: Empty binary data")
return jsonify({'success': False, 'error': 'Empty binary data'}), 400
else:
# Handle standard JSON base64 upload
print("📄 Detected JSON base64 upload")
data = request.json
if not data or 'image' not in data:
print("❌ Error: No image data provided")
return jsonify({"error": "No image data provided"}), 400
image_base64 = data['image']
user_id = data.get('userId', 'unknown_user')
content_type = data.get('contentType', 'image/jpeg')
# Remove data URL prefix if present
if 'base64,' in image_base64:
image_base64 = image_base64.split('base64,')[1]
image_data = base64.b64decode(image_base64)
print(f"👤 User: {user_id} | Size: {len(image_data) / 1024:.1f}KB")
# Generate clean filename with proper naming convention
timestamp = int(time.time())
uploaded_at = datetime.now(timezone.utc).isoformat() + 'Z'
extension = 'png' if content_type == 'image/png' else 'jpg'
filename = f"profile_{user_id}_{timestamp}.{extension}"
storage_path = f"profiles/{filename}"
# Upload to Firebase Storage
storage_client = get_firebase_storage()
if not storage_client:
raise Exception("Firebase Storage not available")
bucket = storage_client.bucket()
blob = bucket.blob(storage_path)
# Upload the image
blob.upload_from_string(image_data, content_type=content_type)
# Make the blob publicly accessible
blob.make_public()
download_url = blob.public_url
print(f"✅ Upload successful: {download_url}", flush=True)
return jsonify({
"success": True,
"downloadURL": download_url,
"storagePath": storage_path,
"filename": filename,
"userId": user_id,
"uploadedAt": uploaded_at,
"sizeKB": round(len(image_data) / 1024, 1)
})
except Exception as e:
print(f"❌ Error uploading profile image: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/upload-gatepass', methods=['POST'])
def upload_gatepass():
"""
Upload full gate pass image to Firebase Storage.
Organizes by date: Pass_images/YYYY-MM-DD/studentId_timestamp.png
"""
try:
data = request.json
if not data or 'image' not in data:
return jsonify({"error": "No image data provided"}), 400
user_id = data.get('userId') or data.get('studentId') or data.get('employeeId') or 'unknown_user'
content_type = data.get('contentType', 'image/png')
print(f"Uploading gatepass for user: {user_id}", flush=True)
# Decode base64 image
image_base64 = data['image']
if 'base64,' in image_base64:
image_base64 = image_base64.split('base64,')[1]
image_data = base64.b64decode(image_base64)
# Generate path: Pass_images/YYYY-MM-DD/STUDENTID_TIMESTAMP.png
now = datetime.now(timezone.utc)
date_folder = now.strftime('%Y-%m-%d')
timestamp = int(now.timestamp())
extension = 'png' if content_type == 'image/png' else 'jpg'
filename = f"{user_id}_{timestamp}.{extension}"
storage_path = f"Pass_images/{date_folder}/{filename}"
print(f"📁 Storage path: {storage_path}")
# Upload to Firebase Storage
storage = get_firebase_storage()
if not storage:
raise Exception("Firebase Storage not available")
bucket = storage.bucket()
blob = bucket.blob(storage_path)
blob.upload_from_string(image_data, content_type=content_type)
blob.make_public()
print(f"Gatepass uploaded: {blob.public_url}", flush=True)
return jsonify({
"success": True,
"downloadURL": blob.public_url,
"storagePath": storage_path,
"filename": filename,
"uploadedAt": now.isoformat() + 'Z'
})
except Exception as e:
print(f"Error uploading gatepass: {e}", flush=True)
return jsonify({"error": str(e)}), 500
@app.route('/upload-visitor-photo', methods=['POST'])
def upload_visitor_photo():
"""
Upload visitor photo from IoT gate to Firebase Storage and create Firestore document.
Organizes by date: visitor/YYYY-MM-DD/visitorName_timestamp.jpg
"""
try:
data = request.json
if not data or 'image' not in data:
return jsonify({"error": "No image data provided"}), 400
visitor_name = data.get('visitor_name', 'unknown_visitor')
destination = data.get('destination', '')
storage_path = data.get('storage_path', '')
meeting_person = data.get('meeting_person', '')
# Always set pass_type to visitor for visitor photo uploads
pass_type = data.get('pass_type', 'visitor')
print(f"Uploading visitor photo for: {visitor_name}", flush=True)
# Decode base64 image
image_base64 = data['image']
if 'base64,' in image_base64:
image_base64 = image_base64.split('base64,')[1]
image_data = base64.b64decode(image_base64)
# If storage_path not provided, generate it
if not storage_path:
now = datetime.now(timezone.utc)
date_folder = now.strftime('%Y-%m-%d')
timestamp = int(now.timestamp())
safe_name = visitor_name.replace(' ', '_').lower()
storage_path = f"visitor/{date_folder}/{safe_name}_{timestamp}.jpg"
print(f"📁 Storage path: {storage_path}")
# Upload to Firebase Storage
storage = get_firebase_storage()
if not storage:
raise Exception("Firebase Storage not available")
bucket = storage.bucket()
blob = bucket.blob(storage_path)
blob.upload_from_string(image_data, content_type='image/jpeg')
blob.make_public()
download_url = blob.public_url
print(f"Visitor photo uploaded: {download_url}", flush=True)
# Create Firestore document for mobile app real-time updates
try:
firestore_client = get_firestore_client()
if not firestore_client:
raise Exception("Firestore client not available")
# For SERVER_TIMESTAMP, we need firestore from firebase_admin
from firebase_admin import firestore
doc_ref = firestore_client.collection('visitor_photos').document()
doc_ref.set({
'visitor_name': visitor_name,
'destination': destination,
'meeting_person': meeting_person,
'photo_url': download_url,
'photo_storage_path': storage_path,
'created_at': firestore.SERVER_TIMESTAMP,
'verification_mode': 'entry',
'pass_type': pass_type # Include pass_type in Firestore document
})
doc_id = doc_ref.id
print(f"Created visitor_photos document: {doc_id}", flush=True)
# Also create/update visitor registration log
registration_ref = firestore_client.collection('visitor_registrations').document()
registration_ref.set({
'visitor_name': visitor_name,
'destination': destination,
'meeting_person': meeting_person,
'photo_url': download_url,
'photo_storage_path': storage_path,
'status': 'photo_captured',
'created_at': firestore.SERVER_TIMESTAMP,
'updated_at': firestore.SERVER_TIMESTAMP,
'source': 'iot_gate_verification',
'pass_type': pass_type # Include pass_type in registration document
})
reg_doc_id = registration_ref.id
print(f"Created visitor_registrations document: {reg_doc_id}", flush=True)
return jsonify({
"success": True,
"downloadURL": download_url,
"storagePath": storage_path,
"documentId": doc_id,
"registrationDocId": reg_doc_id,
"visitor_name": visitor_name,
"uploadedAt": datetime.now(timezone.utc).isoformat() + 'Z'
})
except Exception as firestore_error:
print(f"Firestore document creation failed: {firestore_error}", flush=True)
return jsonify({
"success": True,
"downloadURL": download_url,
"storagePath": storage_path,
"visitor_name": visitor_name,
"firestore_error": str(firestore_error),
"uploadedAt": datetime.now(timezone.utc).isoformat() + 'Z'
})
except Exception as e:
print(f"Error uploading visitor photo: {e}", flush=True)
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
print("DEBUG: Entering main block", flush=True)
debug_mode = True
enable_push_dispatcher = os.getenv('ENABLE_PUSH_DISPATCHER', '1') == '1'
is_reloader_child = os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
if enable_push_dispatcher and ((not debug_mode) or is_reloader_child):
try:
from push_dispatcher import start_push_dispatcher_in_background
start_push_dispatcher_in_background()
print("DEBUG: Push dispatcher started", flush=True)
except Exception as push_err:
print(f"DEBUG: Push dispatcher failed to start: {push_err}", flush=True)
print("Face Verification Server starting on port 5000...", flush=True)
app.run(host='0.0.0.0', port=5000, debug=debug_mode)