Mark-Lasfar
commited on
Commit
·
8a9ebcc
1
Parent(s):
4e9a771
Update Model
Browse files- .env +1 -0
- Dockerfile +6 -1
- api/__pycache__/database.cpython-311.pyc +0 -0
- api/auth.py +16 -2
- api/endpoints.py +2 -1
- api/models.py +0 -63
- init_db.py +52 -0
- main.py +4 -3
.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
SQLALCHEMY_DATABASE_URL=sqlite:///data/mgzon_users.db
|
Dockerfile
CHANGED
|
@@ -10,11 +10,13 @@ RUN apt-get update && apt-get install -y \
|
|
| 10 |
gcc \
|
| 11 |
libc-dev \
|
| 12 |
ffmpeg \
|
|
|
|
| 13 |
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 14 |
|
| 15 |
# Update pip
|
| 16 |
RUN pip install --upgrade pip
|
| 17 |
|
|
|
|
| 18 |
RUN pip install packaging torch==2.4.1
|
| 19 |
|
| 20 |
# Copy requirements.txt and install dependencies
|
|
@@ -30,9 +32,12 @@ COPY . .
|
|
| 30 |
# Verify files in /app
|
| 31 |
RUN ls -R /app
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
# Expose port 7860 for FastAPI
|
| 34 |
EXPOSE 7860
|
| 35 |
|
| 36 |
# Run the FastAPI app
|
| 37 |
CMD ["python", "main.py"]
|
| 38 |
-
|
|
|
|
| 10 |
gcc \
|
| 11 |
libc-dev \
|
| 12 |
ffmpeg \
|
| 13 |
+
sqlite3 \
|
| 14 |
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 15 |
|
| 16 |
# Update pip
|
| 17 |
RUN pip install --upgrade pip
|
| 18 |
|
| 19 |
+
# Install torch and dependencies
|
| 20 |
RUN pip install packaging torch==2.4.1
|
| 21 |
|
| 22 |
# Copy requirements.txt and install dependencies
|
|
|
|
| 32 |
# Verify files in /app
|
| 33 |
RUN ls -R /app
|
| 34 |
|
| 35 |
+
# Initialize the database
|
| 36 |
+
ENV SQLALCHEMY_DATABASE_URL=sqlite:////data/mgzon_users.db
|
| 37 |
+
RUN python init_db.py
|
| 38 |
+
|
| 39 |
# Expose port 7860 for FastAPI
|
| 40 |
EXPOSE 7860
|
| 41 |
|
| 42 |
# Run the FastAPI app
|
| 43 |
CMD ["python", "main.py"]
|
|
|
api/__pycache__/database.cpython-311.pyc
ADDED
|
Binary file (5.63 kB). View file
|
|
|
api/auth.py
CHANGED
|
@@ -11,6 +11,7 @@ from fastapi_users.models import UP
|
|
| 11 |
from typing import Optional
|
| 12 |
import os
|
| 13 |
import logging
|
|
|
|
| 14 |
from api.models import UserRead, UserCreate, UserUpdate
|
| 15 |
from sqlalchemy import select
|
| 16 |
|
|
@@ -71,6 +72,8 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
|
|
| 71 |
associate_by_email: bool = False,
|
| 72 |
is_verified_by_default: bool = False,
|
| 73 |
) -> UP:
|
|
|
|
|
|
|
| 74 |
oauth_account_dict = {
|
| 75 |
"oauth_name": oauth_name,
|
| 76 |
"access_token": access_token,
|
|
@@ -89,20 +92,29 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
|
|
| 89 |
existing_oauth_account = result.scalars().first()
|
| 90 |
|
| 91 |
if existing_oauth_account is not None:
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
if associate_by_email:
|
| 95 |
-
|
| 96 |
statement = select(User).where(User.email == account_email)
|
| 97 |
result = self.user_db.session.execute(statement)
|
| 98 |
user = result.scalars().first()
|
| 99 |
if user is not None:
|
|
|
|
| 100 |
oauth_account.user_id = user.id
|
| 101 |
self.user_db.session.add(oauth_account)
|
| 102 |
self.user_db.session.commit()
|
|
|
|
| 103 |
return await self.on_after_login(user, request)
|
| 104 |
|
| 105 |
# Create new user
|
|
|
|
| 106 |
user_dict = {
|
| 107 |
"email": account_email,
|
| 108 |
"hashed_password": self.password_helper.hash("dummy_password"),
|
|
@@ -113,10 +125,12 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
|
|
| 113 |
self.user_db.session.add(user)
|
| 114 |
self.user_db.session.commit()
|
| 115 |
self.user_db.session.refresh(user)
|
|
|
|
| 116 |
|
| 117 |
oauth_account.user_id = user.id
|
| 118 |
self.user_db.session.add(oauth_account)
|
| 119 |
self.user_db.session.commit()
|
|
|
|
| 120 |
return await self.on_after_login(user, request)
|
| 121 |
|
| 122 |
async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
|
|
|
|
| 11 |
from typing import Optional
|
| 12 |
import os
|
| 13 |
import logging
|
| 14 |
+
from api.database import User, OAuthAccount
|
| 15 |
from api.models import UserRead, UserCreate, UserUpdate
|
| 16 |
from sqlalchemy import select
|
| 17 |
|
|
|
|
| 72 |
associate_by_email: bool = False,
|
| 73 |
is_verified_by_default: bool = False,
|
| 74 |
) -> UP:
|
| 75 |
+
logger.info(f"Processing OAuth callback for {oauth_name} with account_id: {account_id}, email: {account_email}")
|
| 76 |
+
|
| 77 |
oauth_account_dict = {
|
| 78 |
"oauth_name": oauth_name,
|
| 79 |
"access_token": access_token,
|
|
|
|
| 92 |
existing_oauth_account = result.scalars().first()
|
| 93 |
|
| 94 |
if existing_oauth_account is not None:
|
| 95 |
+
logger.info(f"Found existing OAuth account for {oauth_name}, account_id: {account_id}")
|
| 96 |
+
user = existing_oauth_account.user
|
| 97 |
+
if user is None:
|
| 98 |
+
logger.error(f"No user associated with OAuth account {account_id}")
|
| 99 |
+
raise ValueError("No user associated with this OAuth account")
|
| 100 |
+
logger.info(f"Returning existing user: {user.email}")
|
| 101 |
+
return await self.on_after_login(user, request)
|
| 102 |
|
| 103 |
if associate_by_email:
|
| 104 |
+
logger.info(f"Associating by email: {account_email}")
|
| 105 |
statement = select(User).where(User.email == account_email)
|
| 106 |
result = self.user_db.session.execute(statement)
|
| 107 |
user = result.scalars().first()
|
| 108 |
if user is not None:
|
| 109 |
+
logger.info(f"Found existing user by email: {user.email}")
|
| 110 |
oauth_account.user_id = user.id
|
| 111 |
self.user_db.session.add(oauth_account)
|
| 112 |
self.user_db.session.commit()
|
| 113 |
+
logger.info(f"Associated OAuth account with user: {user.email}")
|
| 114 |
return await self.on_after_login(user, request)
|
| 115 |
|
| 116 |
# Create new user
|
| 117 |
+
logger.info(f"Creating new user for email: {account_email}")
|
| 118 |
user_dict = {
|
| 119 |
"email": account_email,
|
| 120 |
"hashed_password": self.password_helper.hash("dummy_password"),
|
|
|
|
| 125 |
self.user_db.session.add(user)
|
| 126 |
self.user_db.session.commit()
|
| 127 |
self.user_db.session.refresh(user)
|
| 128 |
+
logger.info(f"Created new user: {user.email}")
|
| 129 |
|
| 130 |
oauth_account.user_id = user.id
|
| 131 |
self.user_db.session.add(oauth_account)
|
| 132 |
self.user_db.session.commit()
|
| 133 |
+
logger.info(f"Linked OAuth account to new user: {user.email}")
|
| 134 |
return await self.on_after_login(user, request)
|
| 135 |
|
| 136 |
async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
|
api/endpoints.py
CHANGED
|
@@ -3,7 +3,8 @@ import os
|
|
| 3 |
import uuid
|
| 4 |
from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File
|
| 5 |
from fastapi.responses import StreamingResponse
|
| 6 |
-
from api.
|
|
|
|
| 7 |
from api.auth import current_active_user
|
| 8 |
from api.database import get_db
|
| 9 |
from sqlalchemy.orm import Session
|
|
|
|
| 3 |
import uuid
|
| 4 |
from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File
|
| 5 |
from fastapi.responses import StreamingResponse
|
| 6 |
+
from api.database import User, Conversation, Message
|
| 7 |
+
from api.models import QueryRequest, ConversationOut, ConversationCreate, UserUpdate
|
| 8 |
from api.auth import current_active_user
|
| 9 |
from api.database import get_db
|
| 10 |
from sqlalchemy.orm import Session
|
api/models.py
CHANGED
|
@@ -1,70 +1,7 @@
|
|
| 1 |
-
# models.py
|
| 2 |
-
from fastapi_users.db import SQLAlchemyBaseUserTable
|
| 3 |
-
from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey, DateTime
|
| 4 |
-
from sqlalchemy.orm import relationship
|
| 5 |
-
from sqlalchemy.ext.declarative import declarative_base
|
| 6 |
from pydantic import BaseModel, Field
|
| 7 |
from typing import List, Optional
|
| 8 |
from fastapi_users import schemas
|
| 9 |
from datetime import datetime
|
| 10 |
-
import uuid
|
| 11 |
-
|
| 12 |
-
Base = declarative_base()
|
| 13 |
-
|
| 14 |
-
# جدول OAuth Accounts لتخزين بيانات تسجيل الدخول الخارجي
|
| 15 |
-
class OAuthAccount(Base):
|
| 16 |
-
__tablename__ = "oauth_accounts"
|
| 17 |
-
id = Column(Integer, primary_key=True)
|
| 18 |
-
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
| 19 |
-
oauth_name = Column(String, nullable=False)
|
| 20 |
-
access_token = Column(String, nullable=False)
|
| 21 |
-
expires_at = Column(Integer, nullable=True)
|
| 22 |
-
refresh_token = Column(String, nullable=True)
|
| 23 |
-
account_id = Column(String, index=True, nullable=False)
|
| 24 |
-
account_email = Column(String, nullable=False)
|
| 25 |
-
user = relationship("User", back_populates="oauth_accounts")
|
| 26 |
-
|
| 27 |
-
# نموذج المستخدم
|
| 28 |
-
class User(SQLAlchemyBaseUserTable, Base):
|
| 29 |
-
__tablename__ = "users"
|
| 30 |
-
id = Column(Integer, primary_key=True, index=True)
|
| 31 |
-
email = Column(String, unique=True, index=True, nullable=False)
|
| 32 |
-
hashed_password = Column(String, nullable=True)
|
| 33 |
-
is_active = Column(Boolean, default=True)
|
| 34 |
-
is_superuser = Column(Boolean, default=False)
|
| 35 |
-
display_name = Column(String, nullable=True) # الاسم المستعار للمستخدم
|
| 36 |
-
preferred_model = Column(String, nullable=True) # النموذج المفضل (اسم وهمي)
|
| 37 |
-
job_title = Column(String, nullable=True) # الوظيفة
|
| 38 |
-
education = Column(String, nullable=True) # التعليم
|
| 39 |
-
interests = Column(Text, nullable=True) # الاهتمامات
|
| 40 |
-
additional_info = Column(Text, nullable=True) # معلومات إضافية
|
| 41 |
-
conversation_style = Column(String, nullable=True) # نمط المحادثة (مثل: موجز، تحليلي)
|
| 42 |
-
oauth_accounts = relationship("OAuthAccount", back_populates="user", cascade="all, delete-orphan")
|
| 43 |
-
conversations = relationship("Conversation", back_populates="user", cascade="all, delete-orphan")
|
| 44 |
-
|
| 45 |
-
# نموذج المحادثة
|
| 46 |
-
class Conversation(Base):
|
| 47 |
-
__tablename__ = "conversations"
|
| 48 |
-
id = Column(Integer, primary_key=True, index=True)
|
| 49 |
-
conversation_id = Column(String, unique=True, index=True, default=lambda: str(uuid.uuid4()))
|
| 50 |
-
title = Column(String, nullable=True)
|
| 51 |
-
user_id = Column(Integer, ForeignKey("users.id"))
|
| 52 |
-
created_at = Column(DateTime, default=datetime.utcnow)
|
| 53 |
-
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
| 54 |
-
|
| 55 |
-
user = relationship("User", back_populates="conversations")
|
| 56 |
-
messages = relationship("Message", back_populates="conversation", cascade="all, delete-orphan")
|
| 57 |
-
|
| 58 |
-
# نموذج الرسالة
|
| 59 |
-
class Message(Base):
|
| 60 |
-
__tablename__ = "messages"
|
| 61 |
-
id = Column(Integer, primary_key=True, index=True)
|
| 62 |
-
role = Column(String)
|
| 63 |
-
content = Column(Text)
|
| 64 |
-
conversation_id = Column(Integer, ForeignKey("conversations.id"))
|
| 65 |
-
created_at = Column(DateTime, default=datetime.utcnow)
|
| 66 |
-
|
| 67 |
-
conversation = relationship("Conversation", back_populates="messages")
|
| 68 |
|
| 69 |
# Pydantic schemas for fastapi-users
|
| 70 |
class UserRead(schemas.BaseUser[int]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
from typing import List, Optional
|
| 3 |
from fastapi_users import schemas
|
| 4 |
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# Pydantic schemas for fastapi-users
|
| 7 |
class UserRead(schemas.BaseUser[int]):
|
init_db.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from sqlalchemy import create_engine, select, delete
|
| 3 |
+
from sqlalchemy.orm import sessionmaker
|
| 4 |
+
from api.database import Base, User, OAuthAccount, Conversation, Message
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
# Setup logging
|
| 8 |
+
logging.basicConfig(level=logging.INFO)
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
# جلب URL قاعدة البيانات من المتغيرات البيئية
|
| 12 |
+
SQLALCHEMY_DATABASE_URL = os.getenv("SQLALCHEMY_DATABASE_URL")
|
| 13 |
+
if not SQLALCHEMY_DATABASE_URL:
|
| 14 |
+
logger.error("SQLALCHEMY_DATABASE_URL is not set in environment variables.")
|
| 15 |
+
raise ValueError("SQLALCHEMY_DATABASE_URL is required.")
|
| 16 |
+
|
| 17 |
+
# إنشاء المحرك
|
| 18 |
+
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
| 19 |
+
|
| 20 |
+
# إعداد الجلسة
|
| 21 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 22 |
+
|
| 23 |
+
def init_db():
|
| 24 |
+
logger.info("Starting database initialization...")
|
| 25 |
+
|
| 26 |
+
# إنشاء الجداول
|
| 27 |
+
Base.metadata.create_all(bind=engine)
|
| 28 |
+
logger.info("Database tables created successfully.")
|
| 29 |
+
|
| 30 |
+
# تنظيف البيانات غير المتسقة
|
| 31 |
+
with SessionLocal() as session:
|
| 32 |
+
# حذف سجلات oauth_accounts اللي مش مرتبطة بمستخدم موجود
|
| 33 |
+
stmt = delete(OAuthAccount).where(
|
| 34 |
+
OAuthAccount.user_id.notin_(select(User.id))
|
| 35 |
+
)
|
| 36 |
+
result = session.execute(stmt)
|
| 37 |
+
deleted_count = result.rowcount
|
| 38 |
+
session.commit()
|
| 39 |
+
logger.info(f"Deleted {deleted_count} orphaned OAuth accounts.")
|
| 40 |
+
|
| 41 |
+
# التأكد من إن كل المستخدمين ليهم is_active=True
|
| 42 |
+
users = session.execute(select(User)).scalars().all()
|
| 43 |
+
for user in users:
|
| 44 |
+
if not user.is_active:
|
| 45 |
+
user.is_active = True
|
| 46 |
+
logger.info(f"Updated user {user.email} to is_active=True")
|
| 47 |
+
session.commit()
|
| 48 |
+
|
| 49 |
+
logger.info("Database initialization completed.")
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
init_db()
|
main.py
CHANGED
|
@@ -14,8 +14,8 @@ from fastapi.openapi.docs import get_swagger_ui_html
|
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from api.endpoints import router as api_router
|
| 16 |
from api.auth import fastapi_users, auth_backend, current_active_user, get_auth_router
|
| 17 |
-
from api.database import get_db
|
| 18 |
-
from api.models import
|
| 19 |
from motor.motor_asyncio import AsyncIOMotorClient
|
| 20 |
from pydantic import BaseModel
|
| 21 |
from typing import List
|
|
@@ -28,6 +28,7 @@ from hashlib import md5
|
|
| 28 |
from datetime import datetime
|
| 29 |
from httpx_oauth.exceptions import GetIdEmailError
|
| 30 |
import re
|
|
|
|
| 31 |
|
| 32 |
# Setup logging
|
| 33 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -79,8 +80,8 @@ CONCURRENCY_LIMIT = int(os.getenv("CONCURRENCY_LIMIT", 20))
|
|
| 79 |
# Initialize FastAPI app
|
| 80 |
@asynccontextmanager
|
| 81 |
async def lifespan(app: FastAPI):
|
|
|
|
| 82 |
await setup_mongo_index()
|
| 83 |
-
Base.metadata.create_all(bind=engine) # Create tables on startup
|
| 84 |
yield
|
| 85 |
|
| 86 |
app = FastAPI(title="MGZon Chatbot API", lifespan=lifespan)
|
|
|
|
| 14 |
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
from api.endpoints import router as api_router
|
| 16 |
from api.auth import fastapi_users, auth_backend, current_active_user, get_auth_router
|
| 17 |
+
from api.database import get_db
|
| 18 |
+
from api.models import UserRead, UserCreate, UserUpdate
|
| 19 |
from motor.motor_asyncio import AsyncIOMotorClient
|
| 20 |
from pydantic import BaseModel
|
| 21 |
from typing import List
|
|
|
|
| 28 |
from datetime import datetime
|
| 29 |
from httpx_oauth.exceptions import GetIdEmailError
|
| 30 |
import re
|
| 31 |
+
from init_db import init_db # استيراد دالة init_db
|
| 32 |
|
| 33 |
# Setup logging
|
| 34 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 80 |
# Initialize FastAPI app
|
| 81 |
@asynccontextmanager
|
| 82 |
async def lifespan(app: FastAPI):
|
| 83 |
+
init_db() # استدعاء دالة init_db لإنشاء الجداول وتنظيف البيانات
|
| 84 |
await setup_mongo_index()
|
|
|
|
| 85 |
yield
|
| 86 |
|
| 87 |
app = FastAPI(title="MGZon Chatbot API", lifespan=lifespan)
|