Spaces:
Sleeping
Sleeping
Login API implemented
Browse files- .env +1 -0
- Dockerfile +9 -7
- README.md +6 -5
- app/api/login.py +0 -23
- app/api/userchat.py +12 -0
- app/api/userlogin.py +56 -0
- app/api/userlogout.py +14 -0
- app/api/userupload.py +11 -0
- app/dependencies.py +25 -0
- app/main.py +26 -13
- app/utils/chat_rag.py +2 -0
- app/utils/db.py +76 -0
- app/utils/ec_image_utils.py +0 -27
- app/utils/jwt_utils.py +48 -0
- docker-compose.yml +18 -0
- requirements.txt +2 -1
.env
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
EC_ADMIN_PWD='$2b$12$zybxm7XMoGCVV3ovNDcXt.r2QJUhtj7miYfEfuBw9UGqViTIRFg72'
|
Dockerfile
CHANGED
@@ -12,28 +12,30 @@ RUN useradd -m -u 1000 user
|
|
12 |
# Set environment variables for the non-root user
|
13 |
ENV HOME=/home/user \
|
14 |
PATH=/home/user/.local/bin:$PATH \
|
15 |
-
NAME=EduConnect
|
16 |
-
EC_ADMIN_PWD='$2b$12$zybxm7XMoGCVV3ovNDcXt.r2QJUhtj7miYfEfuBw9UGqViTIRFg72'
|
17 |
|
18 |
# Set the non-root user's home directory as the working directory
|
19 |
WORKDIR $HOME
|
20 |
|
21 |
-
# Copy the application files into the non-root user's home directory, ensuring the user owns the copied files
|
22 |
-
COPY --chown=user:user . ./app
|
23 |
-
|
24 |
# Create the /home/user/data directory and ensure it has the correct permissions
|
25 |
RUN mkdir -p ./data && chown user:user ./data
|
26 |
|
27 |
# Change to the non-root user
|
28 |
USER user
|
29 |
|
30 |
-
# Set the working directory to where the application files
|
31 |
WORKDIR $HOME/app
|
32 |
|
|
|
|
|
|
|
33 |
# Install any needed packages specified in requirements.txt
|
34 |
# As the non-root user, ensure packages are installed to the user's home directory
|
35 |
RUN pip install --no-cache-dir --user -r requirements.txt
|
36 |
|
|
|
|
|
|
|
37 |
# Make port 7860 available to the world outside this container
|
38 |
EXPOSE 7860
|
39 |
|
@@ -42,4 +44,4 @@ EXPOSE 7860
|
|
42 |
VOLUME /home/user/data
|
43 |
|
44 |
# Run the FastAPI application using Uvicorn, binding to port 7860
|
45 |
-
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
12 |
# Set environment variables for the non-root user
|
13 |
ENV HOME=/home/user \
|
14 |
PATH=/home/user/.local/bin:$PATH \
|
15 |
+
NAME=EduConnect
|
|
|
16 |
|
17 |
# Set the non-root user's home directory as the working directory
|
18 |
WORKDIR $HOME
|
19 |
|
|
|
|
|
|
|
20 |
# Create the /home/user/data directory and ensure it has the correct permissions
|
21 |
RUN mkdir -p ./data && chown user:user ./data
|
22 |
|
23 |
# Change to the non-root user
|
24 |
USER user
|
25 |
|
26 |
+
# Set the working directory to where the application files will be located
|
27 |
WORKDIR $HOME/app
|
28 |
|
29 |
+
# Copy only the requirements.txt first to leverage Docker cache
|
30 |
+
COPY --chown=user:user requirements.txt ./
|
31 |
+
|
32 |
# Install any needed packages specified in requirements.txt
|
33 |
# As the non-root user, ensure packages are installed to the user's home directory
|
34 |
RUN pip install --no-cache-dir --user -r requirements.txt
|
35 |
|
36 |
+
# Copy the rest of the application files into the container
|
37 |
+
COPY --chown=user:user . .
|
38 |
+
|
39 |
# Make port 7860 available to the world outside this container
|
40 |
EXPOSE 7860
|
41 |
|
|
|
44 |
VOLUME /home/user/data
|
45 |
|
46 |
# Run the FastAPI application using Uvicorn, binding to port 7860
|
47 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
@@ -16,13 +16,12 @@ EduConnect/
|
|
16 |
│ ├── __init__.py # Initializes the FastAPI app and global configurations
|
17 |
│ ├── main.py # Entry point for the FastAPI application, defining routes
|
18 |
│ ├── dependencies.py # Dependency utilities for JWT token verification, etc.
|
19 |
-
│ ├── models.py # Database models for ORM
|
20 |
-
│ ├── schemas.py # Pydantic schemas for request and response validation
|
21 |
│ ├── api/
|
22 |
│ │ ├── __init__.py
|
23 |
│ │ ├── userlogin.py # Endpoint for user login functionality
|
24 |
│ │ ├── userlogout.py # Endpoint for user logout functionality
|
25 |
│ │ ├── userchat.py # Endpoint for chat functionality
|
|
|
26 |
│ │ └── userhistory.py # Endpoint for loading chat history
|
27 |
│ ├── admin/
|
28 |
│ │ ├── __init__.py
|
@@ -32,11 +31,13 @@ EduConnect/
|
|
32 |
│ │ └── user_registration.html # Template for user registration page
|
33 |
│ └── utils/
|
34 |
│ ├── __init__.py
|
|
|
|
|
35 |
│ └── ec_image_utils.py # Integrates MTCNN and Facenet for login authentication
|
36 |
├── static/
|
37 |
-
│ ├── css/
|
38 |
-
│ ├── js/
|
39 |
-
│ └── images/
|
40 |
├── Dockerfile # Docker configuration for setting up the environment
|
41 |
├── requirements.txt # Lists all Python library dependencies
|
42 |
└── .env # Environment variables for configuration settings
|
|
|
16 |
│ ├── __init__.py # Initializes the FastAPI app and global configurations
|
17 |
│ ├── main.py # Entry point for the FastAPI application, defining routes
|
18 |
│ ├── dependencies.py # Dependency utilities for JWT token verification, etc.
|
|
|
|
|
19 |
│ ├── api/
|
20 |
│ │ ├── __init__.py
|
21 |
│ │ ├── userlogin.py # Endpoint for user login functionality
|
22 |
│ │ ├── userlogout.py # Endpoint for user logout functionality
|
23 |
│ │ ├── userchat.py # Endpoint for chat functionality
|
24 |
+
│ │ ├── userupload.py # Endpoint for file upload functionality
|
25 |
│ │ └── userhistory.py # Endpoint for loading chat history
|
26 |
│ ├── admin/
|
27 |
│ │ ├── __init__.py
|
|
|
31 |
│ │ └── user_registration.html # Template for user registration page
|
32 |
│ └── utils/
|
33 |
│ ├── __init__.py
|
34 |
+
│ ├── db.py # Centraized DB functions for ChromaDB collections, TinyDB
|
35 |
+
│ ├── jwt_utils.py # Utility for JWT tokens
|
36 |
│ └── ec_image_utils.py # Integrates MTCNN and Facenet for login authentication
|
37 |
├── static/
|
38 |
+
│ ├── css/ # CSS for the administration portal
|
39 |
+
│ ├── js/ # Javascripts if any for administration portal
|
40 |
+
│ └── images/ # UI rendering images for administration page
|
41 |
├── Dockerfile # Docker configuration for setting up the environment
|
42 |
├── requirements.txt # Lists all Python library dependencies
|
43 |
└── .env # Environment variables for configuration settings
|
app/api/login.py
DELETED
@@ -1,23 +0,0 @@
|
|
1 |
-
# # app/api/login.py
|
2 |
-
# from fastapi import APIRouter, HTTPException, Depends, File, UploadFile
|
3 |
-
# from ..utils.authentication import verify_user
|
4 |
-
# from ..schemas import LoginSchema
|
5 |
-
# from ..crud import get_user_by_email, create_access_token
|
6 |
-
|
7 |
-
# router = APIRouter()
|
8 |
-
|
9 |
-
# @router.post("/login/")
|
10 |
-
# async def login(user_image: UploadFile = File(...)):
|
11 |
-
# # Use MTCNN and Facenet to embed the image and verify user
|
12 |
-
# user_verified, user_email = verify_user(user_image.file)
|
13 |
-
# if not user_verified:
|
14 |
-
# raise HTTPException(status_code=400, detail="Authentication Failed")
|
15 |
-
|
16 |
-
# # Query ChromaDB for similarity and retrieve user details
|
17 |
-
# user = get_user_by_email(user_email)
|
18 |
-
# if not user:
|
19 |
-
# raise HTTPException(status_code=404, detail="User not found")
|
20 |
-
|
21 |
-
# # Generate JWT session token
|
22 |
-
# access_token = create_access_token(data={"sub": user.email})
|
23 |
-
# return {"access_token": access_token, "token_type": "bearer"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/userchat.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException, Body
|
2 |
+
from ..dependencies import get_current_user
|
3 |
+
from typing import Any
|
4 |
+
|
5 |
+
router = APIRouter()
|
6 |
+
|
7 |
+
@router.post("/user/chat")
|
8 |
+
async def chat_with_llama(user_input: str = Body(..., embed=True), current_user: Any = Depends(get_current_user)):
|
9 |
+
# Implement your logic to interact with LlamaV2 LLM here.
|
10 |
+
# Example response, replace with actual chat logic
|
11 |
+
chat_response = "Hello, how can I assist you today?"
|
12 |
+
return {"response": chat_response}
|
app/api/userlogin.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime, timedelta
|
2 |
+
from typing import Optional
|
3 |
+
from fastapi import APIRouter, File, UploadFile, HTTPException
|
4 |
+
from ..utils.db import tinydb_helper, chromadb_face_helper
|
5 |
+
from ..utils.jwt_utils import create_access_token
|
6 |
+
from ..utils.ec_image_utils import get_user_cropped_image_from_photo
|
7 |
+
import os
|
8 |
+
import uuid
|
9 |
+
|
10 |
+
router = APIRouter()
|
11 |
+
L2_FACE_THRESHOLD = 0.85 # distance value closer to 0 =>best match, >1 =>poor match
|
12 |
+
|
13 |
+
async def verify_user_face(file_path: str) -> Optional[dict]:
|
14 |
+
# Assuming `get_user_cropped_image_from_photo` returns the cropped face as expected by ChromaDB
|
15 |
+
face_img = get_user_cropped_image_from_photo(file_path)
|
16 |
+
if face_img is None:
|
17 |
+
return None
|
18 |
+
|
19 |
+
# Query the user's face in ChromaDB
|
20 |
+
query_results = chromadb_face_helper.query_user_face(face_img)
|
21 |
+
if query_results and len(query_results["ids"][0]) > 0:
|
22 |
+
|
23 |
+
chromadb_face_helper.print_query_results(query_results)
|
24 |
+
|
25 |
+
# Assuming the first result is the best match
|
26 |
+
l2_distance = query_results["distances"][0][0]
|
27 |
+
if l2_distance < L2_FACE_THRESHOLD: # l2 distance threshold for top matched face
|
28 |
+
user_id = query_results["ids"][0][0]
|
29 |
+
metadata = query_results["metadatas"][0][0]
|
30 |
+
return {"user_id": user_id, "metadata":metadata}
|
31 |
+
return None
|
32 |
+
|
33 |
+
@router.post("/user/login")
|
34 |
+
async def user_login(file: UploadFile = File(...)):
|
35 |
+
file_path = f"/tmp/{uuid.uuid4()}.jpg" # Generates a unique filename
|
36 |
+
with open(file_path, "wb") as buffer:
|
37 |
+
contents = await file.read()
|
38 |
+
buffer.write(contents)
|
39 |
+
|
40 |
+
# Perform face verification
|
41 |
+
verification_result = await verify_user_face(file_path)
|
42 |
+
if verification_result:
|
43 |
+
user_id = verification_result["user_id"]
|
44 |
+
metadata = verification_result["metadata"]
|
45 |
+
# Generate JWT token with user information
|
46 |
+
access_token = create_access_token(data={"sub": user_id, "name": metadata["name"], "role": metadata["role"]})
|
47 |
+
|
48 |
+
# Calculate expiration time for the token
|
49 |
+
expires_at = (datetime.utcnow() + timedelta(minutes=30)).isoformat() # Example expiration time
|
50 |
+
|
51 |
+
# Store the token in TinyDB
|
52 |
+
tinydb_helper.insert_token(user_id, access_token, expires_at)
|
53 |
+
|
54 |
+
return {"access_token": access_token, "token_type": "bearer"}
|
55 |
+
else:
|
56 |
+
raise HTTPException(status_code=400, detail="Face not recognized")
|
app/api/userlogout.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
2 |
+
from ..utils.db import tinydb_helper # Ensure this import is correct based on your project structure
|
3 |
+
from ..dependencies import oauth2_scheme
|
4 |
+
|
5 |
+
router = APIRouter()
|
6 |
+
|
7 |
+
@router.post("/user/logout")
|
8 |
+
async def user_logout(token: str = Depends(oauth2_scheme)):
|
9 |
+
try:
|
10 |
+
# Invalidate the token by removing it from the database
|
11 |
+
tinydb_helper.remove_token_by_value(token)
|
12 |
+
return {"message": "User logged out successfully"}
|
13 |
+
except Exception as e:
|
14 |
+
raise HTTPException(status_code=400, detail=f"Error during logout: {str(e)}")
|
app/api/userupload.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, UploadFile, File
|
2 |
+
|
3 |
+
router = APIRouter()
|
4 |
+
|
5 |
+
@router.post("/user/upload")
|
6 |
+
async def upload_file(file: UploadFile = File(...)):
|
7 |
+
file_location = f"/path/to/your/uploads/{file.filename}"
|
8 |
+
with open(file_location, "wb") as buffer:
|
9 |
+
contents = await file.read()
|
10 |
+
buffer.write(contents)
|
11 |
+
return {"filename": file.filename, "location": file_location}
|
app/dependencies.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import Depends, HTTPException, status
|
2 |
+
from fastapi.security import OAuth2PasswordBearer
|
3 |
+
from jose import jwt, JWTError
|
4 |
+
from .utils.db import tinydb_helper # Ensure correct import path
|
5 |
+
from .utils.jwt_utils import SECRET_KEY, ALGORITHM # Ensure these are defined in your jwt_utils.py
|
6 |
+
|
7 |
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
8 |
+
|
9 |
+
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
10 |
+
credentials_exception = HTTPException(
|
11 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
12 |
+
detail="Could not validate credentials",
|
13 |
+
headers={"WWW-Authenticate": "Bearer"},
|
14 |
+
)
|
15 |
+
try:
|
16 |
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
17 |
+
user_id: str = payload.get("sub")
|
18 |
+
if user_id is None:
|
19 |
+
raise credentials_exception
|
20 |
+
token_data = tinydb_helper.query_token(user_id, token)
|
21 |
+
if not token_data:
|
22 |
+
raise credentials_exception
|
23 |
+
return token_data # Or return a user model based on the token_data
|
24 |
+
except JWTError:
|
25 |
+
raise credentials_exception
|
app/main.py
CHANGED
@@ -7,20 +7,13 @@ from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
|
7 |
from fastapi.templating import Jinja2Templates
|
8 |
|
9 |
from .admin import admin_functions as admin
|
10 |
-
from .utils.
|
|
|
|
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
# Persitent storage for chromadb setup in /data volume
|
15 |
-
ec_client = chromadb.PersistentClient("/home/user/data/chromadb")
|
16 |
-
user_faces_db = ec_client.get_or_create_collection(name="user_faces_db", embedding_function=UserFaceEmbeddingFunction())
|
17 |
-
|
18 |
-
@app.on_event("startup")
|
19 |
-
async def startup_event():
|
20 |
-
# Perform any necessary ChromaDB setup or checks here
|
21 |
-
ec_client.heartbeat()
|
22 |
-
print("ChromaDB setup completed.")
|
23 |
|
|
|
24 |
|
25 |
# Add middleware
|
26 |
# Set all origins to wildcard for simplicity, but you should limit this in production
|
@@ -32,6 +25,21 @@ app.add_middleware(
|
|
32 |
allow_headers=["*"],
|
33 |
)
|
34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
# Mount static files
|
36 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
37 |
|
@@ -68,4 +76,9 @@ async def handle_user_registration(request: Request, email: str = Form(...), nam
|
|
68 |
return templates.TemplateResponse("registration_success.html", {"request": request})
|
69 |
else:
|
70 |
# Reload registration page with error message
|
71 |
-
return templates.TemplateResponse("user_registration.html", {"request": request, "error": "Registration failed"})
|
|
|
|
|
|
|
|
|
|
|
|
7 |
from fastapi.templating import Jinja2Templates
|
8 |
|
9 |
from .admin import admin_functions as admin
|
10 |
+
from .utils.db import UserFaceEmbeddingFunction,ChromaDBFaceHelper
|
11 |
+
from .api import userlogin, userlogout, userchat, userupload
|
12 |
+
from .utils.db import tinydb_helper, ChromaDBFaceHelper
|
13 |
|
14 |
+
CHROMADB_LOC = "/home/user/data/chromadb"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
+
app = FastAPI()
|
17 |
|
18 |
# Add middleware
|
19 |
# Set all origins to wildcard for simplicity, but you should limit this in production
|
|
|
25 |
allow_headers=["*"],
|
26 |
)
|
27 |
|
28 |
+
# Persitent storage for chromadb setup in /data volume
|
29 |
+
ec_client = chromadb.PersistentClient(CHROMADB_LOC)
|
30 |
+
# The following collection reference is needed for admin function to register face
|
31 |
+
user_faces_db = ec_client.get_or_create_collection(name="user_faces_db", embedding_function=UserFaceEmbeddingFunction())
|
32 |
+
|
33 |
+
|
34 |
+
@app.on_event("startup")
|
35 |
+
async def startup_event():
|
36 |
+
global chromadb_face_helper
|
37 |
+
# Assuming chromadb persistent store client for APIs is in helper
|
38 |
+
db_path = CHROMADB_LOC
|
39 |
+
chromadb_face_helper = ChromaDBFaceHelper(db_path) # Used by APIs
|
40 |
+
# Perform any other startup tasks here
|
41 |
+
|
42 |
+
|
43 |
# Mount static files
|
44 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
45 |
|
|
|
76 |
return templates.TemplateResponse("registration_success.html", {"request": request})
|
77 |
else:
|
78 |
# Reload registration page with error message
|
79 |
+
return templates.TemplateResponse("user_registration.html", {"request": request, "error": "Registration failed"})
|
80 |
+
|
81 |
+
app.include_router(userlogin.router)
|
82 |
+
app.include_router(userlogout.router)
|
83 |
+
app.include_router(userchat.router)
|
84 |
+
app.include_router(userupload.router)
|
app/utils/chat_rag.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
# Model import
|
2 |
+
# Implement RAG using wvvocer
|
app/utils/db.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from tinydb import TinyDB, Query, where
|
3 |
+
from tinydb.storages import MemoryStorage
|
4 |
+
import chromadb
|
5 |
+
from chromadb.api.types import EmbeddingFunction, Embeddings, Image, Images
|
6 |
+
from keras_facenet import FaceNet
|
7 |
+
from typing import Any
|
8 |
+
from datetime import datetime, timedelta
|
9 |
+
|
10 |
+
CHROMADB_LOC = "/home/user/data/chromadb"
|
11 |
+
|
12 |
+
class TinyDBHelper:
|
13 |
+
def __init__(self):
|
14 |
+
self.db = TinyDB(storage=MemoryStorage)
|
15 |
+
self.tokens_table = self.db.table('tokens')
|
16 |
+
|
17 |
+
def insert_token(self, user_id: str, token: str, expires_at: str):
|
18 |
+
self.tokens_table.insert({'user_id': user_id, 'token': token, 'expires_at': expires_at})
|
19 |
+
|
20 |
+
def query_token(self, user_id: str, token: str) -> bool:
|
21 |
+
"""Query to check if the token exists and is valid."""
|
22 |
+
User = Query()
|
23 |
+
# Assuming your tokens table contains 'user_id', 'token', and 'expires_at'
|
24 |
+
result = self.tokens_table.search((User.user_id == user_id) & (User.token == token))
|
25 |
+
# Optionally, check if the token is expired
|
26 |
+
expires_at = datetime.fromisoformat(result[0]['expires_at'])
|
27 |
+
if datetime.utcnow() > expires_at:
|
28 |
+
return False
|
29 |
+
|
30 |
+
return bool(result)
|
31 |
+
|
32 |
+
def remove_token_by_value(self, token: str):
|
33 |
+
"""Remove a token based on its value."""
|
34 |
+
self.tokens_table.remove((where('token') == token))
|
35 |
+
|
36 |
+
###### Class implementing Custom Embedding function for chroma db
|
37 |
+
#
|
38 |
+
class UserFaceEmbeddingFunction(EmbeddingFunction[Images]):
|
39 |
+
def __init__(self):
|
40 |
+
# Intitialize the FaceNet model
|
41 |
+
self.facenet = FaceNet()
|
42 |
+
|
43 |
+
def __call__(self, input: Images) -> Embeddings:
|
44 |
+
# Since the input images are assumed to be `numpy.ndarray` objects already,
|
45 |
+
# we can directly use them for embeddings extraction without additional processing.
|
46 |
+
# Ensure the input images are pre-cropped face images ready for embedding extraction.
|
47 |
+
|
48 |
+
# Extract embeddings using FaceNet for the pre-cropped face images.
|
49 |
+
embeddings_array = self.facenet.embeddings(input)
|
50 |
+
|
51 |
+
# Convert numpy array of embeddings to list of lists, as expected by Chroma.
|
52 |
+
return embeddings_array.tolist()
|
53 |
+
|
54 |
+
|
55 |
+
# Usage example:
|
56 |
+
# user_face_embedding_function = UserFaceEmbeddingFunction()
|
57 |
+
# Assuming `images` is a list of `numpy.ndarray` objects where each represents a pre-cropped face image ready for embedding extraction.
|
58 |
+
# embeddings = user_face_embedding_function(images)
|
59 |
+
|
60 |
+
|
61 |
+
class ChromaDBFaceHelper:
|
62 |
+
def __init__(self, db_path: str):
|
63 |
+
self.client = chromadb.PersistentClient(db_path)
|
64 |
+
self.user_faces_db = self.client.get_or_create_collection(name="user_faces_db", embedding_function=UserFaceEmbeddingFunction())
|
65 |
+
|
66 |
+
def query_user_face(self, presented_face: Any, n_results: int = 1):
|
67 |
+
return self.user_faces_db.query(query_images=[presented_face], n_results=n_results)
|
68 |
+
|
69 |
+
def print_query_results(self, query_results: dict) -> None:
|
70 |
+
for id, distance, metadata in zip(query_results["ids"][0], query_results['distances'][0], query_results['metadatas'][0]):
|
71 |
+
print(f'id: {id}, distance: {distance}, metadata: {metadata}')
|
72 |
+
|
73 |
+
|
74 |
+
# Initialize these helpers globally if they are to be used across multiple modules
|
75 |
+
tinydb_helper = TinyDBHelper()
|
76 |
+
chromadb_face_helper = ChromaDBFaceHelper(CHROMADB_LOC) # Initialization requires db_path
|
app/utils/ec_image_utils.py
CHANGED
@@ -1,9 +1,6 @@
|
|
1 |
import cv2
|
2 |
from mtcnn.mtcnn import MTCNN
|
3 |
import numpy as np
|
4 |
-
from keras_facenet import FaceNet
|
5 |
-
from chromadb.api.types import EmbeddingFunction, Embeddings, Image, Images
|
6 |
-
from typing import List
|
7 |
|
8 |
|
9 |
def load_image(filename):
|
@@ -79,27 +76,3 @@ def get_user_cropped_image_from_photo(filename):
|
|
79 |
return cropped_face[0]
|
80 |
|
81 |
|
82 |
-
###### Class implementing Custom Embedding function for chroma db
|
83 |
-
#
|
84 |
-
class UserFaceEmbeddingFunction(EmbeddingFunction[Images]):
|
85 |
-
def __init__(self):
|
86 |
-
# Intitialize the FaceNet model
|
87 |
-
self.facenet = FaceNet()
|
88 |
-
|
89 |
-
def __call__(self, input: Images) -> Embeddings:
|
90 |
-
# Since the input images are assumed to be `numpy.ndarray` objects already,
|
91 |
-
# we can directly use them for embeddings extraction without additional processing.
|
92 |
-
# Ensure the input images are pre-cropped face images ready for embedding extraction.
|
93 |
-
|
94 |
-
# Extract embeddings using FaceNet for the pre-cropped face images.
|
95 |
-
embeddings_array = self.facenet.embeddings(input)
|
96 |
-
|
97 |
-
# Convert numpy array of embeddings to list of lists, as expected by Chroma.
|
98 |
-
return embeddings_array.tolist()
|
99 |
-
|
100 |
-
|
101 |
-
# Usage example:
|
102 |
-
# user_face_embedding_function = UserFaceEmbeddingFunction()
|
103 |
-
# Assuming `images` is a list of `numpy.ndarray` objects where each represents a pre-cropped face image ready for embedding extraction.
|
104 |
-
# embeddings = user_face_embedding_function(images)
|
105 |
-
|
|
|
1 |
import cv2
|
2 |
from mtcnn.mtcnn import MTCNN
|
3 |
import numpy as np
|
|
|
|
|
|
|
4 |
|
5 |
|
6 |
def load_image(filename):
|
|
|
76 |
return cropped_face[0]
|
77 |
|
78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/utils/jwt_utils.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime, timedelta
|
2 |
+
from jose import JWTError, jwt
|
3 |
+
from typing import Any, Union
|
4 |
+
from tinydb import TinyDB, Query
|
5 |
+
from tinydb.storages import MemoryStorage
|
6 |
+
|
7 |
+
|
8 |
+
# Secret key to encode JWT tokens. In production, use a more secure key and keep it secret!
|
9 |
+
SECRET_KEY = "a_very_secret_key"
|
10 |
+
ALGORITHM = "HS256"
|
11 |
+
ACCESS_TOKEN_EXPIRE_MINUTES = 30 # The expiration time for the access token
|
12 |
+
|
13 |
+
db = TinyDB(storage=MemoryStorage)
|
14 |
+
tokens_table = db.table('tokens')
|
15 |
+
|
16 |
+
def insert_token(user_id: str, token: str, expires_in: timedelta):
|
17 |
+
expiration = datetime.utcnow() + expires_in
|
18 |
+
tokens_table.insert({'user_id': user_id, 'token': token, 'expires_at': expiration.isoformat()})
|
19 |
+
|
20 |
+
def validate_token(user_id: str, token: str) -> bool:
|
21 |
+
User = Query()
|
22 |
+
result = tokens_table.search((User.user_id == user_id) & (User.token == token))
|
23 |
+
if not result:
|
24 |
+
return False
|
25 |
+
# Check token expiration
|
26 |
+
expires_at = datetime.fromisoformat(result[0]['expires_at'])
|
27 |
+
if datetime.utcnow() > expires_at:
|
28 |
+
return False
|
29 |
+
return True
|
30 |
+
|
31 |
+
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:
|
32 |
+
"""
|
33 |
+
Creates a JWT access token.
|
34 |
+
|
35 |
+
:param data: A dictionary of claims (e.g., {"sub": user_id}) to include in the token.
|
36 |
+
:param expires_delta: A timedelta object representing how long the token is valid.
|
37 |
+
:return: A JWT token as a string.
|
38 |
+
"""
|
39 |
+
to_encode = data.copy()
|
40 |
+
if expires_delta:
|
41 |
+
expire = datetime.utcnow() + expires_delta
|
42 |
+
else:
|
43 |
+
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
44 |
+
to_encode.update({"exp": expire})
|
45 |
+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
46 |
+
return encoded_jwt
|
47 |
+
|
48 |
+
# Additional functions can be added here for verifying tokens, decoding tokens, etc.
|
docker-compose.yml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: '3.8'
|
2 |
+
services:
|
3 |
+
educonnect:
|
4 |
+
image: python:3.9
|
5 |
+
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860", "--reload"]
|
6 |
+
volumes:
|
7 |
+
- /Users/tyago/Workspace/EduConnect:/home/user/app
|
8 |
+
ports:
|
9 |
+
- "7860:7860"
|
10 |
+
environment:
|
11 |
+
- HOME=/home/user
|
12 |
+
- PATH=/home/user/.local/bin:$PATH
|
13 |
+
- NAME=EduConnect
|
14 |
+
- EC_ADMIN_PWD=$2b$12$zybxm7XMoGCVV3ovNDcXt.r2QJUhtj7miYfEfuBw9UGqViTIRFg72
|
15 |
+
user: "1000:1000"
|
16 |
+
working_dir: /home/user/app
|
17 |
+
env_file:
|
18 |
+
- .env
|
requirements.txt
CHANGED
@@ -13,4 +13,5 @@ jinja2==3.0.* # For Admin site redndering
|
|
13 |
bcrypt==4.1.* # For hashing secrets
|
14 |
opencv-python-headless==4.5.5.* # For image handling images
|
15 |
tensorflow # Tensorflow is needed by MTCNN for facial recognition
|
16 |
-
scipy # The scipy is required for keras-facenet
|
|
|
|
13 |
bcrypt==4.1.* # For hashing secrets
|
14 |
opencv-python-headless==4.5.5.* # For image handling images
|
15 |
tensorflow # Tensorflow is needed by MTCNN for facial recognition
|
16 |
+
scipy # The scipy is required for keras-facenet
|
17 |
+
tinydb # The in memory database for storing JWT tokens
|