update(server): connect api to erp and add func remove user
This commit is contained in:
parent
6a1e19ec7f
commit
a594ba5ba2
|
|
@ -4,3 +4,4 @@ images
|
||||||
uploads
|
uploads
|
||||||
log.log
|
log.log
|
||||||
venv
|
venv
|
||||||
|
.env
|
||||||
|
|
@ -1,56 +1,44 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import requests
|
import requests
|
||||||
from fastapi import UploadFile
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
URL_API = "http://172.16.6.38:8000/api/v1"
|
load_dotenv()
|
||||||
|
|
||||||
|
HOST = os.getenv("MS_HOST", "http://10.20.2.26:3002")
|
||||||
|
|
||||||
|
|
||||||
def send_image(id, image_bytes, student_name: str, status: str):
|
|
||||||
id = str(id)
|
|
||||||
|
|
||||||
|
def sync_checkin(email: str, timestamp_ms: int, image_data: bytes, student_name: str, status: str):
|
||||||
today = datetime.datetime.now().strftime("%Y_%m_%d")
|
today = datetime.datetime.now().strftime("%Y_%m_%d")
|
||||||
folder_path = f"./images/{today}"
|
folder_path = f"./images/{today}"
|
||||||
os.makedirs(folder_path, exist_ok=True)
|
os.makedirs(folder_path, exist_ok=True)
|
||||||
|
|
||||||
safe_student = "".join(c for c in student_name if c.isalnum() or c in ("-", "_"))
|
safe_student = "".join(c for c in student_name if c.isalnum() or c in ("-", "_"))
|
||||||
safe_status = "".join(c for c in status if c.isalnum() or c in ("-", "_"))
|
safe_status = "".join(c for c in status if c.isalnum() or c in ("-", "_"))
|
||||||
timestamp = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
ts_str = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||||
|
file_path = os.path.join(folder_path, f"{safe_student}_{safe_status}_at_{ts_str}.png")
|
||||||
file_name = f"{safe_student}_{safe_status}_at_{timestamp}.png"
|
|
||||||
file_path = os.path.join(folder_path, file_name)
|
|
||||||
|
|
||||||
# Lưu xuống
|
|
||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
f.write(image_bytes)
|
f.write(image_data)
|
||||||
|
|
||||||
# Gửi API
|
image_b64 = "data:image/jpeg;base64," + base64.b64encode(image_data).decode("utf-8")
|
||||||
|
payload = {
|
||||||
|
"email": email,
|
||||||
|
"time": timestamp_ms,
|
||||||
|
"image": image_b64,
|
||||||
|
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
with open(file_path, "rb") as image_file:
|
response = requests.post(HOST + "/api/log-time/check-in-out", json=payload)
|
||||||
response = requests.post(
|
|
||||||
URL_API + "/admin/tracking/send-image",
|
|
||||||
data={"id": id, "file_name": file_name},
|
|
||||||
files={"image": image_file}
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
res = response.json()
|
||||||
|
print("[sync_checkin] response:", res)
|
||||||
|
return res
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Send image failed:", e)
|
print("[sync_checkin] failed:", e)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def create_history(data):
|
|
||||||
# Gửi yêu cầu POST với dữ liệu đã chỉ định
|
|
||||||
response = requests.post(URL_API+"/admin/tracking/scan-create", data=data)
|
|
||||||
res = response.json()
|
|
||||||
|
|
||||||
print(res)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def users(params):
|
|
||||||
# Gửi yêu cầu POST với dữ liệu đã chỉ định
|
|
||||||
response = requests.get(URL_API+"/admin/timekeeping", params=params, headers={"authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL21zLnByb2xvZ3kubmV0L2FwaS92MS9hZG1pbi9sb2dpbiIsImlhdCI6MTc1Njg2MDQ1OSwiZXhwIjoxNzg4Mzk2NDU5LCJuYmYiOjE3NTY4NjA0NTksImp0aSI6IkRrb0NLbHBKV1pkNnZCN0QiLCJzdWIiOiIxNSIsInBydiI6ImQyZmYyOTMzOWE4YTNlODJjMzU4MmE1YThlNzM5ZGYxNzg5YmIxMmYifQ.DoHqHeAGGxpvzlNQ9dAZjZf2Yl573XCgNBT8ZiSx5N4"})
|
|
||||||
res = response.json()
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,13 @@ class CheckingApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteUser(id: string | number) {
|
||||||
|
return await axios({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/users/${id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async checkin({ file }: { file: any }) {
|
async checkin({ file }: { file: any }) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file, "frame.jpg");
|
formData.append("file", file, "frame.jpg");
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,51 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { checkingApi } from "@/api/checking-api";
|
import { checkingApi } from "@/api/checking-api";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Button } from "@/components/ui/button";
|
||||||
import { TabsContent } from "@/components/ui/tabs";
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
|
import { useConfirm } from "@/components/confirm-modal-provider";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import useAppStore from "@/stores/use-app-store";
|
import useAppStore from "@/stores/use-app-store";
|
||||||
import useUserStore from "@/stores/use-user-store";
|
import useUserStore from "@/stores/use-user-store";
|
||||||
import { Users } from "lucide-react";
|
import { Trash2, Users } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export default function TabUsers({ value }: { value: string }) {
|
export default function TabUsers({ value }: { value: string }) {
|
||||||
const [users, setUsers] = useState<IUser[]>([]);
|
const [users, setUsers] = useState<IUser[]>([]);
|
||||||
const { currentUser, setCurrentUser } = useUserStore();
|
const { currentUser, setCurrentUser } = useUserStore();
|
||||||
const { refreshUsers, setRefreshUsers } = useAppStore();
|
const { refreshUsers, setRefreshUsers } = useAppStore();
|
||||||
|
const confirm = useConfirm();
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await checkingApi.users();
|
const { data } = await checkingApi.users();
|
||||||
|
|
||||||
setUsers(data);
|
setUsers(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (user: IUser, e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const ok = await confirm({
|
||||||
|
title: "Xóa người dùng",
|
||||||
|
message: `Bạn có chắc muốn xóa "${user.name}"? Toàn bộ lịch sử điểm danh sẽ bị xóa theo.`,
|
||||||
|
confirmText: "Xóa",
|
||||||
|
cancelText: "Hủy",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
if (!ok) return;
|
||||||
|
try {
|
||||||
|
await checkingApi.deleteUser(user.id);
|
||||||
|
toast.success(`Đã xóa ${user.name}`);
|
||||||
|
if (currentUser?.id === user.id) setCurrentUser(null);
|
||||||
|
setUsers((prev) => prev.filter((u) => u.id !== user.id));
|
||||||
|
} catch {
|
||||||
|
toast.error("Xóa thất bại");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggle = (data: IUser) => {
|
const toggle = (data: IUser) => {
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
if (data.id === currentUser.id) {
|
if (data.id === currentUser.id) {
|
||||||
|
|
@ -51,17 +73,16 @@ export default function TabUsers({ value }: { value: string }) {
|
||||||
<TabsContent value={value} className="">
|
<TabsContent value={value} className="">
|
||||||
<div className="flex flex-col gap-2 flex-1 p-4 space-y-2 overflow-y-auto h-[90vh]">
|
<div className="flex flex-col gap-2 flex-1 p-4 space-y-2 overflow-y-auto h-[90vh]">
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<Card
|
<div
|
||||||
key={user.id}
|
key={user.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-4 cursor-pointer transition-all duration-200 hover:shadow-md hover:scale-[1.01] select-none",
|
"flex items-center gap-3 p-4 rounded-xl border shadow-sm bg-card cursor-pointer transition-all duration-200 hover:shadow-md hover:scale-[1.01] select-none",
|
||||||
currentUser?.id === user.id &&
|
currentUser?.id === user.id &&
|
||||||
"bg-blue-50 dark:bg-blue-950 border-blue-500 shadow-md"
|
"bg-blue-50 dark:bg-blue-950 border-blue-500 shadow-md"
|
||||||
)}
|
)}
|
||||||
onClick={() => toggle(user)}
|
onClick={() => toggle(user)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<Avatar className="size-12 shrink-0">
|
||||||
<Avatar className="size-12">
|
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
src={
|
src={
|
||||||
`https://ms.prology.net/image/storage/${user?.avatar}` || ""
|
`https://ms.prology.net/image/storage/${user?.avatar}` || ""
|
||||||
|
|
@ -70,46 +91,24 @@ export default function TabUsers({ value }: { value: string }) {
|
||||||
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
|
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="font-semibold text-gray-900 dark:text-gray-100">
|
<h4 className="font-semibold text-gray-900 dark:text-gray-100 truncate">
|
||||||
{user.name}
|
{user.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
<p className="text-sm text-gray-600 dark:text-gray-400 truncate">
|
||||||
{user.email}
|
{user.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <DropdownMenu>
|
<Button
|
||||||
<DropdownMenuTrigger
|
variant="ghost"
|
||||||
asChild
|
size="icon"
|
||||||
onClick={(e) => e.stopPropagation()}
|
className="h-8 w-8 shrink-0 text-red-500 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950"
|
||||||
>
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
||||||
<MoreVertical className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Mở menu</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" className="w-48">
|
|
||||||
<DropdownMenuItem onClick={(e) => handleViewDetails(user, e)}>
|
|
||||||
<UserCheck className="mr-2 h-4 w-4" />
|
|
||||||
<span>Xem chi tiết</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={(e) => handleEdit(user, e)}>
|
|
||||||
<Edit className="mr-2 h-4 w-4" />
|
|
||||||
<span>Chỉnh sửa</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => handleDelete(user, e)}
|
onClick={(e) => handleDelete(user, e)}
|
||||||
className="text-red-600 focus:text-red-600 dark:text-red-400 dark:focus:text-red-400"
|
|
||||||
>
|
>
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
<span>Xóa</span>
|
</Button>
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu> */}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
|
|
||||||
# DATABASE_URL = "mysql+pymysql://root:root@localhost/face_checkin_au?charset=utf8mb4"
|
DATABASE_URL = "mysql+pymysql://root:root@localhost/face_checkin_au?charset=utf8mb4"
|
||||||
DATABASE_URL = "mysql+pymysql://admin:Work1234%^@localhost/face_checkin_au?charset=utf8mb4"
|
# DATABASE_URL = "mysql+pymysql://admin:Work1234%^@localhost/face_checkin_au?charset=utf8mb4"
|
||||||
|
|
||||||
engine = create_engine(DATABASE_URL)
|
engine = create_engine(DATABASE_URL)
|
||||||
SessionLocal = sessionmaker(bind=engine, autoflush=False)
|
SessionLocal = sessionmaker(bind=engine, autoflush=False)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from models import Base, Student, CheckInLog, StudentEncoding
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from api import create_history, send_image
|
from api import sync_checkin
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
@ -23,6 +23,7 @@ logging.basicConfig(level=logging.INFO)
|
||||||
_enc_matrix: np.ndarray | None = None # shape (N, 128)
|
_enc_matrix: np.ndarray | None = None # shape (N, 128)
|
||||||
_enc_student_ids: np.ndarray | None = None # shape (N,) int64
|
_enc_student_ids: np.ndarray | None = None # shape (N,) int64
|
||||||
_enc_student_names: dict = {}
|
_enc_student_names: dict = {}
|
||||||
|
_enc_student_emails: dict = {}
|
||||||
_cache_lock = threading.Lock()
|
_cache_lock = threading.Lock()
|
||||||
_cache_dirty = True
|
_cache_dirty = True
|
||||||
|
|
||||||
|
|
@ -33,20 +34,20 @@ def invalidate_encoding_cache():
|
||||||
|
|
||||||
|
|
||||||
def _load_encoding_cache(db):
|
def _load_encoding_cache(db):
|
||||||
global _enc_matrix, _enc_student_ids, _enc_student_names, _cache_dirty
|
global _enc_matrix, _enc_student_ids, _enc_student_names, _enc_student_emails, _cache_dirty
|
||||||
with _cache_lock:
|
with _cache_lock:
|
||||||
if not _cache_dirty and _enc_matrix is not None:
|
if not _cache_dirty and _enc_matrix is not None:
|
||||||
return _enc_matrix, _enc_student_ids, _enc_student_names
|
return _enc_matrix, _enc_student_ids, _enc_student_names, _enc_student_emails
|
||||||
|
|
||||||
rows = db.execute(
|
rows = db.execute(
|
||||||
text("""
|
text("""
|
||||||
SELECT s.id AS student_id, s.name AS student_name, se.encoding AS encoding_blob
|
SELECT s.id AS student_id, s.name AS student_name, s.email AS student_email, se.encoding AS encoding_blob
|
||||||
FROM student_encodings se
|
FROM student_encodings se
|
||||||
JOIN students s ON s.id = se.student_id
|
JOIN students s ON s.id = se.student_id
|
||||||
""")
|
""")
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
encodings, student_ids, names = [], [], {}
|
encodings, student_ids, names, emails = [], [], {}, {}
|
||||||
for r in rows:
|
for r in rows:
|
||||||
try:
|
try:
|
||||||
enc = np.frombuffer(r.encoding_blob, dtype=np.float64)
|
enc = np.frombuffer(r.encoding_blob, dtype=np.float64)
|
||||||
|
|
@ -54,6 +55,7 @@ def _load_encoding_cache(db):
|
||||||
encodings.append(enc)
|
encodings.append(enc)
|
||||||
student_ids.append(r.student_id)
|
student_ids.append(r.student_id)
|
||||||
names[r.student_id] = r.student_name
|
names[r.student_id] = r.student_name
|
||||||
|
emails[r.student_id] = r.student_email
|
||||||
else:
|
else:
|
||||||
logging.warning(f"encoding size invalid for student {r.student_id}: {enc.size}")
|
logging.warning(f"encoding size invalid for student {r.student_id}: {enc.size}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -67,9 +69,10 @@ def _load_encoding_cache(db):
|
||||||
_enc_student_ids = np.array([], dtype=np.int64)
|
_enc_student_ids = np.array([], dtype=np.int64)
|
||||||
|
|
||||||
_enc_student_names = names
|
_enc_student_names = names
|
||||||
|
_enc_student_emails = emails
|
||||||
_cache_dirty = False
|
_cache_dirty = False
|
||||||
logging.info(f"Encoding cache loaded: {_enc_matrix.shape[0]} encodings, {len(names)} students")
|
logging.info(f"Encoding cache loaded: {_enc_matrix.shape[0]} encodings, {len(names)} students")
|
||||||
return _enc_matrix, _enc_student_ids, _enc_student_names
|
return _enc_matrix, _enc_student_ids, _enc_student_names, _enc_student_emails
|
||||||
|
|
||||||
|
|
||||||
# --- Image preprocessing (Phương án 3: resize trước khi detect) ---
|
# --- Image preprocessing (Phương án 3: resize trước khi detect) ---
|
||||||
|
|
@ -272,7 +275,7 @@ async def checkin(background_tasks: BackgroundTasks, file: UploadFile = File(...
|
||||||
DIST_THRESHOLD = 0.42
|
DIST_THRESHOLD = 0.42
|
||||||
|
|
||||||
# Phương án 1: dùng cache RAM thay vì query DB mỗi request
|
# Phương án 1: dùng cache RAM thay vì query DB mỗi request
|
||||||
enc_matrix, enc_sids, enc_names = _load_encoding_cache(db)
|
enc_matrix, enc_sids, enc_names, enc_emails = _load_encoding_cache(db)
|
||||||
|
|
||||||
if enc_matrix.shape[0] == 0:
|
if enc_matrix.shape[0] == 0:
|
||||||
return {"message": "No known encodings in DB.", "status": False}
|
return {"message": "No known encodings in DB.", "status": False}
|
||||||
|
|
@ -353,14 +356,13 @@ async def checkin(background_tasks: BackgroundTasks, file: UploadFile = File(...
|
||||||
log_id = insert_result.lastrowid
|
log_id = insert_result.lastrowid
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
def _sync_to_ms(name: str, time_string: str, img_data: bytes, local_status: str, checkin_log_id: int):
|
def _sync_to_ms(email: str, timestamp_ms: int, img_data: bytes, name: str, local_status: str, checkin_log_id: int):
|
||||||
try:
|
try:
|
||||||
# Gửi thông tin check-in lên MS server để tạo history
|
res = sync_checkin(email, timestamp_ms, img_data, name, local_status)
|
||||||
ms_response = create_history({"name": name.split('\n')[0], "time_string": time_string, "status": local_status})
|
check_in_flag = res.get("data", {}).get("checkIn")
|
||||||
id_log = ms_response.get('data', {}).get('id', 0)
|
if check_in_flag is None:
|
||||||
ms_status = ms_response.get('data', {}).get('status', local_status)
|
return
|
||||||
|
ms_status = "check in" if check_in_flag else "check out"
|
||||||
# Nếu MS server trả về status khác với status local thì đồng bộ lại DB
|
|
||||||
if ms_status != local_status:
|
if ms_status != local_status:
|
||||||
fix_db = SessionLocal()
|
fix_db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
|
|
@ -369,25 +371,21 @@ async def checkin(background_tasks: BackgroundTasks, file: UploadFile = File(...
|
||||||
{"status": ms_status, "id": checkin_log_id}
|
{"status": ms_status, "id": checkin_log_id}
|
||||||
)
|
)
|
||||||
fix_db.commit()
|
fix_db.commit()
|
||||||
logging.info(f"Corrected log #{checkin_log_id} status: {local_status} → {ms_status}")
|
logging.info(f"Corrected log #{checkin_log_id}: {local_status} → {ms_status}")
|
||||||
finally:
|
finally:
|
||||||
fix_db.close()
|
fix_db.close()
|
||||||
|
|
||||||
# Upload ảnh check-in lên MS server gắn với log id vừa tạo
|
|
||||||
send_image(id_log, img_data, name, ms_status)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"MS sync error: {e}")
|
logging.error(f"MS sync error: {e}")
|
||||||
|
|
||||||
# Chạy đồng bộ MS ở background để không block response trả về client
|
background_tasks.add_task(
|
||||||
# TODO: bỏ comment khi deploy thật
|
_sync_to_ms,
|
||||||
# background_tasks.add_task(
|
enc_emails.get(best_student, ""),
|
||||||
# _sync_to_ms,
|
int(now.timestamp() * 1000),
|
||||||
# enc_names.get(best_student),
|
image_data,
|
||||||
# f"{datetime.datetime.now()}",
|
enc_names.get(best_student, ""),
|
||||||
# image_data,
|
status,
|
||||||
# status,
|
log_id,
|
||||||
# log_id,
|
)
|
||||||
# )
|
|
||||||
|
|
||||||
student = db.execute(
|
student = db.execute(
|
||||||
text("""
|
text("""
|
||||||
|
|
@ -485,3 +483,20 @@ def get_users(db: Session = Depends(get_db)):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/users/{user_id}")
|
||||||
|
def delete_user(user_id: int, db: Session = Depends(get_db)):
|
||||||
|
student = db.execute(
|
||||||
|
text("SELECT id FROM students WHERE id = :id"),
|
||||||
|
{"id": user_id}
|
||||||
|
).fetchone()
|
||||||
|
if not student:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
|
db.execute(text("DELETE FROM student_encodings WHERE student_id = :id"), {"id": user_id})
|
||||||
|
db.execute(text("DELETE FROM checkin_logs WHERE student_id = :id"), {"id": user_id})
|
||||||
|
db.execute(text("DELETE FROM students WHERE id = :id"), {"id": user_id})
|
||||||
|
db.commit()
|
||||||
|
invalidate_encoding_cache()
|
||||||
|
return {"message": "User deleted successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ numpy
|
||||||
opencv-python
|
opencv-python
|
||||||
requests
|
requests
|
||||||
pymysql
|
pymysql
|
||||||
|
python-dotenv
|
||||||
# pip install -r requirements.txt
|
# pip install -r requirements.txt
|
||||||
# sudo apt-get install cmake or brew install cmake
|
# sudo apt-get install cmake or brew install cmake
|
||||||
# pip install dlib
|
# pip install dlib
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -8,12 +8,12 @@
|
||||||
<script
|
<script
|
||||||
type="module"
|
type="module"
|
||||||
crossorigin
|
crossorigin
|
||||||
src="au/checkin/assets/index-yYwv6FSW.js"
|
src="/au/checkin/static/assets/index-BKsPQIjb.js"
|
||||||
></script>
|
></script>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
crossorigin
|
crossorigin
|
||||||
href="au/checkin/assets/index-CDZdzCu6.css"
|
href="/au/checkin/static/assets/index-BTDrLopT.css"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue