import os import math import jwt import datetime from functools import wraps from flask import Flask, request, jsonify, send_file from flask_cors import CORS from firebase_admin import credentials, initialize_app, db import firebase_admin from werkzeug.utils import secure_filename from openpyxl import Workbook # === KONFIGURASI === app = Flask(__name__) CORS(app) app.config["SECRET_KEY"] = "absensiwajah-secret-key" # === FIREBASE === cred = credentials.Certificate("serviceAccountKey.json") if not firebase_admin._apps: initialize_app(cred, { "databaseURL": "https://absensiwajah-4b6e8-default-rtdb.asia-southeast1.firebasedatabase.app" }) # === Folder Wajah === os.makedirs("faces", exist_ok=True) # === JWT Helpers === def token_required(role=None): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): token = None if "Authorization" in request.headers: token = request.headers["Authorization"].split(" ")[1] if not token: return jsonify({"success": False, "error": "Token missing"}), 401 try: data = jwt.decode(token, app.config["SECRET_KEY"], algorithms=["HS256"]) if role and data.get("role") != role: return jsonify({"success": False, "error": "Unauthorized"}), 403 except Exception as e: return jsonify({"success": False, "error": str(e)}), 401 return f(*args, **kwargs) return wrapper return decorator # === ADMIN LOGIN === @app.route("/admin/login", methods=["POST"]) def admin_login(): data = request.json if data["username"] == "admin" and data["password"] == "admin123": token = jwt.encode({ "username": "admin", "role": "admin", "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=6) }, app.config["SECRET_KEY"], algorithm="HS256") return jsonify({"success": True, "token": token}) return jsonify({"success": False, "error": "Invalid credentials"}), 401 @app.route("/admin/verify-token", methods=["POST"]) def verify_token(): data = request.json try: decoded = jwt.decode(data["token"], app.config["SECRET_KEY"], algorithms=["HS256"]) return jsonify({"success": True, "authenticated": True, "role": decoded["role"]}) except Exception: return jsonify({"success": False, "authenticated": False}), 401 # === REGISTER USER === @app.route("/register", methods=["POST"]) def register_user(): try: user_id = str(int(datetime.datetime.now().timestamp())) name = request.form.get("name") file = request.files["file"] path = os.path.join("faces", f"{user_id}.jpg") file.save(path) db.reference("users").child(user_id).set({ "user_id": user_id, "name": name, "registered_at": datetime.datetime.now().isoformat() }) return jsonify({"success": True, "user_id": user_id}) except Exception as e: return jsonify({"success": False, "error": str(e)}) # === ABSENSI === @app.route("/attendance", methods=["POST"]) def attendance(): try: file = request.files["file"] latitude = request.form.get("latitude") longitude = request.form.get("longitude") # Dummy match users = db.reference("users").get() or {} if not users: return jsonify({"success": False, "error": "Belum ada user terdaftar"}) recognized_user = list(users.values())[0] # match dummy similarity = 0.93 record = { "user_id": recognized_user["user_id"], "name": recognized_user["name"], "timestamp": datetime.datetime.now().isoformat(), "similarity": similarity, "status": "present", "latitude": latitude, "longitude": longitude, "location_verified": True } db.reference("attendance").push(record) return jsonify({"success": True, "recognized_user": recognized_user, "location": record}) except Exception as e: return jsonify({"success": False, "error": str(e)}) # === AMBIL DATA ABSENSI === @app.route("/attendance-records", methods=["GET"]) def attendance_records(): try: data = db.reference("attendance").get() or {} records = list(data.values()) records.sort(key=lambda x: x.get("timestamp", ""), reverse=True) return jsonify({"success": True, "records": records}) except Exception as e: return jsonify({"success": False, "error": str(e)}) # === ADMIN DASHBOARD === @app.route("/admin/dashboard", methods=["GET"]) @token_required("admin") def admin_dashboard(): try: users = db.reference("users").get() or {} attendance = db.reference("attendance").get() or {} total_users = len(users) total_attendance = len(attendance) avg_score = sum([r.get("similarity", 0) for r in attendance.values()]) / max(len(attendance), 1) return jsonify({ "success": True, "statistics": { "totalUsers": total_users, "totalTransactions": total_attendance, "averageScore": avg_score * 100, "activeMonths": len(set([r["timestamp"][:7] for r in attendance.values()])) } }) except Exception as e: return jsonify({"success": False, "error": str(e)}) # === ADMIN USERS === @app.route("/users", methods=["GET"]) def get_users(): users = db.reference("users").get() or {} return jsonify({"success": True, "users": users}) # === ADMIN DELETE USER === @app.route("/admin/users/", methods=["DELETE"]) @token_required("admin") def delete_user(user_id): try: ref = db.reference("users").child(user_id) user_data = ref.get() if not user_data: return jsonify({"success": False, "error": "User not found"}), 404 ref.delete() face_path = f"faces/{user_id}.jpg" if os.path.exists(face_path): os.remove(face_path) return jsonify({"success": True, "message": f"User {user_id} deleted"}) except Exception as e: return jsonify({"success": False, "error": str(e)}), 500 # === LOKASI SETTINGS === @app.route("/admin/location-settings", methods=["GET", "POST"]) @token_required("admin") def location_settings(): try: ref = db.reference("location_settings") if request.method == "GET": return jsonify({"success": True, "settings": ref.get() or {}}) data = request.json ref.set(data) return jsonify({"success": True, "settings": data}) except Exception as e: return jsonify({"success": False, "error": str(e)}) # === TEST LOCATION === @app.route("/admin/test-location", methods=["POST"]) @token_required("admin") def test_location(): data = request.json settings = db.reference("location_settings").get() if not settings: return jsonify({"success": False, "message": "Location settings not found"}) def haversine(lat1, lon1, lat2, lon2): R = 6371000 phi1, phi2 = math.radians(lat1), math.radians(lat2) dphi, dlambda = math.radians(lat2 - lat1), math.radians(lon2 - lon1) a = math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda / 2) ** 2 return 2 * R * math.atan2(math.sqrt(a), math.sqrt(1 - a)) distance = haversine( data["latitude"], data["longitude"], settings["latitude"], settings["longitude"] ) valid = distance <= settings["radius"] return jsonify({ "success": True, "valid": valid, "distance": round(distance, 2), "max_radius": settings["radius"], "message": "Dalam radius lokasi" if valid else "Diluar radius lokasi" }) if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)