diff --git a/main.py b/main.py index c6ef026..f7263fe 100644 --- a/main.py +++ b/main.py @@ -8,7 +8,7 @@ import datetime from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from database import SessionLocal, engine -from models import Base, Student, CheckInLog +from models import Base, Student, CheckInLog, StudentEncoding from sqlalchemy.exc import IntegrityError from sqlalchemy import text @@ -39,9 +39,6 @@ async def register_face(name: str = Form(...),email: str = Form(...),file: Uploa text("SELECT id FROM students WHERE email = :email"), {"email": email} ).fetchone() - if existing: - db.close() - raise HTTPException(status_code=400, detail="Email đã được đăng ký.") # Save image image_data = await file.read() @@ -60,12 +57,34 @@ async def register_face(name: str = Form(...),email: str = Form(...),file: Uploa encoding_bytes = encodings[0].tobytes() try: - db.execute( - text("INSERT INTO students (name, email, encoding) VALUES (:name, :email, :encoding)"), - {"name": name, "email": email, "encoding": encoding_bytes} - ) - db.commit() - return {"message": "Đăng ký thành công."} + if existing: + # Email exists, just add new encoding + student_id = existing[0] + db.execute( + text("INSERT INTO student_encodings (student_id, encoding) VALUES (:student_id, :encoding)"), + {"student_id": student_id, "encoding": encoding_bytes} + ) + db.commit() + return {"message": "Đã thêm encoding mới cho sinh viên."} + else: + # Email doesn't exist, create new student + db.execute( + text("INSERT INTO students (name, email) VALUES (:name, :email)"), + {"name": name, "email": email} + ) + db.commit() + + # Get the last inserted id + result = db.execute(text("SELECT LAST_INSERT_ID()")).fetchone() + student_id = result[0] + + # Insert encoding + db.execute( + text("INSERT INTO student_encodings (student_id, encoding) VALUES (:student_id, :encoding)"), + {"student_id": student_id, "encoding": encoding_bytes} + ) + db.commit() + return {"message": "Đăng ký thành công."} except IntegrityError: db.rollback() raise HTTPException(status_code=400, detail="Email đã tồn tại.") @@ -87,34 +106,66 @@ async def checkin(file: UploadFile = File(...), camera_id: str = Form("cam1"), d unknown_encoding = unknown_encodings[0] - students = db.query(Student).all() - for student in students: - known_encoding = np.frombuffer(student.encoding) + # Get all encodings with student info + encodings = db.execute( + text(""" + SELECT s.id, s.name, se.encoding + FROM student_encodings se + JOIN students s ON s.id = se.student_id + """) + ).fetchall() + + for encoding in encodings: + known_encoding = np.frombuffer(encoding.encoding) result = face_recognition.compare_faces([known_encoding], unknown_encoding, tolerance=0.5) if result[0]: now = datetime.datetime.now() - recent_check = db.query(CheckInLog).filter( - CheckInLog.student_id == student.id, - CheckInLog.time > now - datetime.timedelta(minutes=5) - ).first() + recent_check = db.execute( + text(""" + SELECT id FROM checkin_logs + WHERE student_id = :student_id + AND time > :time_threshold + """), + { + "student_id": encoding.id, + "time_threshold": now - datetime.timedelta(minutes=5) + } + ).fetchone() if recent_check: - return {"message": f"{student.name} already checked in recently."} + return {"message": f"{encoding.name} already checked in recently."} - log = CheckInLog(student_id=student.id, time=now, camera_id=camera_id) - db.add(log) + db.execute( + text(""" + INSERT INTO checkin_logs (student_id, time, camera_id) + VALUES (:student_id, :time, :camera_id) + """), + { + "student_id": encoding.id, + "time": now, + "camera_id": camera_id + } + ) db.commit() - return {"message": f"Check-in successful for {student.name}"} + return {"message": f"Check-in successful for {encoding.name}"} return {"message": "No match found."} @app.get("/logs") def get_logs(db: Session = Depends(get_db)): - logs = db.query(CheckInLog).all() + logs = db.execute( + text(""" + SELECT s.name, cl.time, cl.camera_id + FROM checkin_logs cl + JOIN students s ON cl.student_id = s.id + ORDER BY cl.time DESC + """) + ).fetchall() + result = [] for log in logs: result.append({ - "name": log.student.name, + "name": log.name, "time": log.time.strftime("%Y-%m-%d %H:%M:%S"), "camera_id": log.camera_id }) diff --git a/models.py b/models.py index e0c71aa..c94b127 100644 --- a/models.py +++ b/models.py @@ -6,16 +6,24 @@ import datetime class Student(Base): __tablename__ = "students" - __tablename__ = "students" - id = Column(Integer, primary_key=True, index=True) name = Column(String(100), nullable=False) email = Column(String(100), nullable=False, unique=True, index=True) - encoding = Column(LargeBinary, nullable=False) __table_args__ = (UniqueConstraint('email', name='uq_student_email'),) checkins = relationship("CheckInLog", back_populates="student") + encodings = relationship("StudentEncoding", back_populates="student") + +class StudentEncoding(Base): + __tablename__ = "student_encodings" + + id = Column(Integer, primary_key=True, index=True) + student_id = Column(Integer, ForeignKey("students.id")) + encoding = Column(LargeBinary, nullable=False) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + + student = relationship("Student", back_populates="encodings") class CheckInLog(Base): __tablename__ = "checkin_logs" diff --git a/static/face-removebg-preview.png b/static/face-removebg-preview.png new file mode 100644 index 0000000..f98bdf8 Binary files /dev/null and b/static/face-removebg-preview.png differ diff --git a/static/index.html b/static/index.html index 19ad68c..a703a28 100644 --- a/static/index.html +++ b/static/index.html @@ -23,7 +23,7 @@ video { width: 100%; - max-width: 400px; + max-width: 1200px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); display: block; @@ -86,6 +86,19 @@ display: flex; justify-content: center; margin-top: 20px; + gap: 10px; + } + + #auto-checkin { + background-color: #f39c12; + } + + #auto-checkin:hover { + background-color: #d35400; + } + + #auto-checkin.active { + background-color: #e74c3c; } .logs-container { @@ -136,14 +149,16 @@ .main-layout { display: flex; gap: 20px; + max-width: 1600px; + margin: 0 auto; } .left-panel { - flex: 1; + flex: 6; } .right-panel { - flex: 1; + flex: 5; } @media (max-width: 768px) { @@ -176,7 +191,6 @@ position: fixed; top: 20px; right: 20px; - background-color: #f87171; /* red-400 */ color: white; padding: 12px 20px; border-radius: 8px; @@ -187,6 +201,14 @@ animation: slideIn 0.3s ease-out; } + .alert-success { + background-color: #34d399; /* green-400 */ + } + + .alert-error { + background-color: #f87171; /* red-400 */ + } + @keyframes slideIn { from { opacity: 0; @@ -204,7 +226,10 @@

📸 Face Camera

- +
+ + Hướng dẫn nhận diện khuôn mặt +

@@ -217,6 +242,7 @@
+
Nhấn phím Space để điểm danh nhanh @@ -246,7 +272,7 @@
- +