Mark-Lasfar commited on
Commit
448d967
·
1 Parent(s): 99365f8

Update backend and server frontend for OAuth JSON response, client-side navigation, and add .gitignore

Browse files
Files changed (4) hide show
  1. api/auth.py +43 -26
  2. api/database.py +130 -70
  3. main.py +30 -18
  4. requirements.txt +6 -5
api/auth.py CHANGED
@@ -1,17 +1,13 @@
1
- # api/auth.py
2
- # SPDX-FileCopyrightText: Hadad <[email protected]>
3
- # SPDX-License-License: Apache-2.0
4
-
5
  from fastapi_users import FastAPIUsers
6
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
7
  from httpx_oauth.clients.google import GoogleOAuth2
8
  from httpx_oauth.clients.github import GitHubOAuth2
9
- from fastapi_users.manager import BaseUserManager, UUIDIDMixin
10
  from fastapi import Depends, Request, FastAPI, Response
11
  from fastapi.responses import JSONResponse
12
- from fastapi_users_db_mongobeanie import MongoBeanieUserDatabase
13
- from api.database import User, get_user_db
14
- from api.models import UserRead, UserCreate, UserUpdate
15
  from typing import Optional
16
  import os
17
  import logging
@@ -20,6 +16,9 @@ import httpx
20
  from datetime import datetime
21
  import jwt
22
 
 
 
 
23
  # إعداد اللوقينج
24
  logger = logging.getLogger(__name__)
25
 
@@ -59,10 +58,25 @@ github_oauth_client._access_token_url = "https://github.com/login/oauth/access_t
59
  github_oauth_client._access_token_params = {"headers": {"Accept": "application/json"}}
60
 
61
  # مدير المستخدمين
62
- class UserManager(UUIDIDMixin, BaseUserManager[User, str]):
63
  reset_password_token_secret = SECRET
64
  verification_token_secret = SECRET
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  async def oauth_callback(
67
  self,
68
  oauth_name: str,
@@ -75,22 +89,25 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, str]):
75
  *,
76
  associate_by_email: bool = False,
77
  is_verified_by_default: bool = False,
78
- ) -> User:
79
  logger.info(f"OAuth callback for {oauth_name} account {account_id}")
80
 
81
- oauth_account_dict = {
82
- "oauth_name": oauth_name,
83
- "access_token": access_token,
84
- "account_id": account_id,
85
- "account_email": account_email,
86
- "expires_at": expires_at,
87
- "refresh_token": refresh_token,
88
- }
89
 
90
- existing_oauth_account = await self.user_db.oauth.get_by_oauth_account(oauth_name, account_id)
91
  if existing_oauth_account:
92
  logger.info(f"Fetching user for OAuth account with user_id: {existing_oauth_account.user_id}")
93
- user = await self.user_db.get(existing_oauth_account.user_id)
 
 
 
94
  if user:
95
  logger.info(f"User found: {user.email}, proceeding with on_after_login")
96
  await self.on_after_login(user, request)
@@ -103,8 +120,8 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, str]):
103
  logger.info(f"Associating OAuth account by email: {account_email}")
104
  user = await self.user_db.get_by_email(account_email)
105
  if user:
106
- oauth_account_dict["user_id"] = user.id
107
- await self.user_db.oauth.create(oauth_account_dict)
108
  logger.info(f"User associated: {user.email}, proceeding with on_after_login")
109
  await self.on_after_login(user, request)
110
  return user
@@ -118,17 +135,17 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, str]):
118
  }
119
 
120
  user = await self.user_db.create(user_dict)
121
- oauth_account_dict["user_id"] = user.id
122
- await self.user_db.oauth.create(oauth_account_dict)
123
  logger.info(f"New user created: {user.email}, proceeding with on_after_login")
124
  await self.on_after_login(user, request)
125
  return user
126
 
127
  # استدعاء user manager من get_user_db
128
- async def get_user_manager(user_db: MongoBeanieUserDatabase = Depends(get_user_db)):
129
  yield UserManager(user_db)
130
 
131
- fastapi_users = FastAPIUsers[User, str](
132
  get_user_db,
133
  [auth_backend],
134
  )
 
 
 
 
 
1
  from fastapi_users import FastAPIUsers
2
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
3
  from httpx_oauth.clients.google import GoogleOAuth2
4
  from httpx_oauth.clients.github import GitHubOAuth2
5
+ from fastapi_users.manager import BaseUserManager, IntegerIDMixin
6
  from fastapi import Depends, Request, FastAPI, Response
7
  from fastapi.responses import JSONResponse
8
+ from sqlalchemy.ext.asyncio import AsyncSession
9
+ from sqlalchemy import select
10
+ from fastapi_users.models import UP
11
  from typing import Optional
12
  import os
13
  import logging
 
16
  from datetime import datetime
17
  import jwt
18
 
19
+ from api.database import User, OAuthAccount, CustomSQLAlchemyUserDatabase, get_user_db
20
+ from api.models import UserRead, UserCreate, UserUpdate
21
+
22
  # إعداد اللوقينج
23
  logger = logging.getLogger(__name__)
24
 
 
58
  github_oauth_client._access_token_params = {"headers": {"Accept": "application/json"}}
59
 
60
  # مدير المستخدمين
61
+ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
62
  reset_password_token_secret = SECRET
63
  verification_token_secret = SECRET
64
 
65
+ async def get_by_oauth_account(self, oauth_name: str, account_id: str):
66
+ logger.info(f"Checking OAuth account: {oauth_name}/{account_id}")
67
+ statement = select(OAuthAccount).where(
68
+ OAuthAccount.oauth_name == oauth_name,
69
+ OAuthAccount.account_id == account_id
70
+ )
71
+ result = await self.user_db.session.execute(statement)
72
+ return result.scalar_one_or_none()
73
+
74
+ async def add_oauth_account(self, oauth_account: OAuthAccount):
75
+ logger.info(f"Adding OAuth account for user {oauth_account.user_id}")
76
+ self.user_db.session.add(oauth_account)
77
+ await self.user_db.session.commit()
78
+ await self.user_db.session.refresh(oauth_account)
79
+
80
  async def oauth_callback(
81
  self,
82
  oauth_name: str,
 
89
  *,
90
  associate_by_email: bool = False,
91
  is_verified_by_default: bool = False,
92
+ ) -> UP:
93
  logger.info(f"OAuth callback for {oauth_name} account {account_id}")
94
 
95
+ oauth_account = OAuthAccount(
96
+ oauth_name=oauth_name,
97
+ access_token=access_token,
98
+ account_id=account_id,
99
+ account_email=account_email,
100
+ expires_at=expires_at,
101
+ refresh_token=refresh_token,
102
+ )
103
 
104
+ existing_oauth_account = await self.get_by_oauth_account(oauth_name, account_id)
105
  if existing_oauth_account:
106
  logger.info(f"Fetching user for OAuth account with user_id: {existing_oauth_account.user_id}")
107
+ statement = select(User).where(User.id == existing_oauth_account.user_id)
108
+ result = await self.user_db.session.execute(statement)
109
+ user = result.scalar_one_or_none()
110
+
111
  if user:
112
  logger.info(f"User found: {user.email}, proceeding with on_after_login")
113
  await self.on_after_login(user, request)
 
120
  logger.info(f"Associating OAuth account by email: {account_email}")
121
  user = await self.user_db.get_by_email(account_email)
122
  if user:
123
+ oauth_account.user_id = user.id
124
+ await self.add_oauth_account(oauth_account)
125
  logger.info(f"User associated: {user.email}, proceeding with on_after_login")
126
  await self.on_after_login(user, request)
127
  return user
 
135
  }
136
 
137
  user = await self.user_db.create(user_dict)
138
+ oauth_account.user_id = user.id
139
+ await self.add_oauth_account(oauth_account)
140
  logger.info(f"New user created: {user.email}, proceeding with on_after_login")
141
  await self.on_after_login(user, request)
142
  return user
143
 
144
  # استدعاء user manager من get_user_db
145
+ async def get_user_manager(user_db: CustomSQLAlchemyUserDatabase = Depends(get_user_db)):
146
  yield UserManager(user_db)
147
 
148
+ fastapi_users = FastAPIUsers[User, int](
149
  get_user_db,
150
  [auth_backend],
151
  )
api/database.py CHANGED
@@ -4,83 +4,143 @@
4
 
5
  import os
6
  import logging
7
- from typing import AsyncGenerator, Optional, Dict, Any
8
  from datetime import datetime
 
9
 
10
- from motor.motor_asyncio import AsyncIOMotorClient
11
- from beanie import Document, init_beanie
12
- from fastapi_users_db_mongobeanie import MongoBeanieUserDatabase
13
- from fastapi_users import models as fu_models
14
- from fastapi_users_db_mongobeanie.oauth import BeanieBaseOAuthAccount
 
 
 
15
 
16
  # إعداد اللوج
17
  logger = logging.getLogger(__name__)
18
 
19
- # جلب MONGO_URI من متغيرات البيئة
20
- MONGO_URI = os.getenv("MONGODB_URI")
21
- if not MONGO_URI:
22
- logger.error("MONGODB_URI is not set in environment variables.")
23
- raise ValueError("MONGODB_URI is required for MongoDB.")
24
-
25
- # إعداد MongoDB
26
- client = AsyncIOMotorClient(MONGO_URI)
27
- mongo_db = client["hager"]
28
-
29
- # تعريف نموذج OAuthAccount
30
- class OAuthAccount(BeanieBaseOAuthAccount, Document):
31
- class Settings:
32
- name = "oauth_account"
33
-
34
- # تعريف نموذج المستخدم
35
- class User(fu_models.UP, fu_models.OAP, Document):
36
- class Settings:
37
- name = "user"
38
-
39
- id: fu_models.ID
40
- email: str
41
- hashed_password: str
42
- is_active: bool = True
43
- is_superuser: bool = False
44
- is_verified: bool = False
45
- display_name: Optional[str] = None
46
- preferred_model: Optional[str] = None
47
- job_title: Optional[str] = None
48
- education: Optional[str] = None
49
- interests: Optional[str] = None
50
- additional_info: Optional[str] = None
51
- conversation_style: Optional[str] = None
52
- oauth_accounts: list[OAuthAccount] = []
53
-
54
- # تعريف نموذج المحادثة
55
- class Conversation(Document):
56
- class Settings:
57
- name = "conversation"
58
-
59
- conversation_id: str
60
- user_id: fu_models.ID
61
- title: str
62
- created_at: datetime = datetime.utcnow()
63
- updated_at: datetime = datetime.utcnow()
64
-
65
- # تعريف نموذج الرسالة
66
- class Message(Document):
67
- class Settings:
68
- name = "message"
69
-
70
- conversation_id: str
71
- role: str
72
- content: str
73
- created_at: datetime = datetime.utcnow()
74
-
75
- # دالة لتهيئة قاعدة البيانات
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  async def init_db():
77
  try:
78
- await init_beanie(database=mongo_db, document_models=[User, OAuthAccount, Conversation, Message])
79
- logger.info("MongoDB initialized successfully")
 
80
  except Exception as e:
81
- logger.error(f"Error initializing MongoDB: {e}")
82
  raise
83
-
84
- # دالة لجلب قاعدة بيانات المستخدمين
85
- async def get_user_db() -> AsyncGenerator[MongoBeanieUserDatabase, None]:
86
- yield MongoBeanieUserDatabase(User, OAuthAccount)
 
4
 
5
  import os
6
  import logging
 
7
  from datetime import datetime
8
+ from typing import AsyncGenerator, Optional, Dict, Any
9
 
10
+ from sqlalchemy import Column, String, Integer, ForeignKey, DateTime, Boolean, Text, select
11
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
12
+ from sqlalchemy.ext.declarative import declarative_base
13
+ from sqlalchemy.orm import relationship
14
+ from fastapi import Depends
15
+ from fastapi_users.db import SQLAlchemyUserDatabase
16
+ from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable # استيراد الصحيح
17
+ import aiosqlite
18
 
19
  # إعداد اللوج
20
  logger = logging.getLogger(__name__)
21
 
22
+ # استخدم القيمة مباشرة إذا لم يكن هناك متغير بيئة
23
+ SQLALCHEMY_DATABASE_URL = os.environ.get(
24
+ "SQLALCHEMY_DATABASE_URL"
25
+ ) or "sqlite+aiosqlite:///./data/mgzon_users.db"
26
+
27
+ # تأكد أن الدرايفر async
28
+ if "aiosqlite" not in SQLALCHEMY_DATABASE_URL:
29
+ raise ValueError("Database URL must use 'sqlite+aiosqlite' for async support")
30
+
31
+ # إنشاء محرك async
32
+ async_engine = create_async_engine(
33
+ SQLALCHEMY_DATABASE_URL,
34
+ echo=True,
35
+ connect_args={"check_same_thread": False}
36
+ )
37
+
38
+ # إعداد الجلسة async
39
+ AsyncSessionLocal = async_sessionmaker(
40
+ async_engine,
41
+ expire_on_commit=False,
42
+ class_=AsyncSession
43
+ )
44
+
45
+ # القاعدة الأساسية للنماذج
46
+ Base = declarative_base()
47
+
48
+ # النماذج (Models)
49
+ class OAuthAccount(Base):
50
+ __tablename__ = "oauth_account"
51
+ id = Column(Integer, primary_key=True, index=True)
52
+ user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
53
+ oauth_name = Column(String, nullable=False)
54
+ access_token = Column(String, nullable=False)
55
+ expires_at = Column(Integer, nullable=True)
56
+ refresh_token = Column(String, nullable=True)
57
+ account_id = Column(String, index=True, nullable=False)
58
+ account_email = Column(String, nullable=False)
59
+
60
+ user = relationship("User", back_populates="oauth_accounts", lazy="selectin")
61
+
62
+ class User(SQLAlchemyBaseUserTable, Base): # بدون [int] في الإصدار 10.4.2
63
+ __tablename__ = "user"
64
+ id = Column(Integer, primary_key=True, index=True)
65
+ email = Column(String, unique=True, index=True, nullable=False)
66
+ hashed_password = Column(String, nullable=False)
67
+ is_active = Column(Boolean, default=True)
68
+ is_superuser = Column(Boolean, default=False)
69
+ is_verified = Column(Boolean, default=False)
70
+ display_name = Column(String, nullable=True)
71
+ preferred_model = Column(String, nullable=True)
72
+ job_title = Column(String, nullable=True)
73
+ education = Column(String, nullable=True)
74
+ interests = Column(String, nullable=True)
75
+ additional_info = Column(Text, nullable=True)
76
+ conversation_style = Column(String, nullable=True)
77
+
78
+ oauth_accounts = relationship("OAuthAccount", back_populates="user", cascade="all, delete-orphan")
79
+ conversations = relationship("Conversation", back_populates="user", cascade="all, delete-orphan")
80
+
81
+ class Conversation(Base):
82
+ __tablename__ = "conversation"
83
+ id = Column(Integer, primary_key=True, index=True)
84
+ conversation_id = Column(String, unique=True, index=True, nullable=False)
85
+ user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
86
+ title = Column(String, nullable=False)
87
+ created_at = Column(DateTime, default=datetime.utcnow)
88
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
89
+
90
+ user = relationship("User", back_populates="conversations")
91
+ messages = relationship("Message", back_populates="conversation", cascade="all, delete-orphan")
92
+
93
+ class Message(Base):
94
+ __tablename__ = "message"
95
+ id = Column(Integer, primary_key=True, index=True)
96
+ conversation_id = Column(Integer, ForeignKey("conversation.id"), nullable=False)
97
+ role = Column(String, nullable=False)
98
+ content = Column(Text, nullable=False)
99
+ created_at = Column(DateTime, default=datetime.utcnow)
100
+
101
+ conversation = relationship("Conversation", back_populates="messages")
102
+
103
+ # قاعدة بيانات المستخدم المخصصة
104
+ class CustomSQLAlchemyUserDatabase(SQLAlchemyUserDatabase):
105
+ def __init__(self, session: AsyncSession, user_table, oauth_account_table=None):
106
+ super().__init__(session, user_table, oauth_account_table)
107
+
108
+ def parse_id(self, value: Any) -> int:
109
+ logger.debug(f"Parsing user id: {value} (type={type(value)})")
110
+ return int(value) if isinstance(value, str) else value
111
+
112
+ async def get_by_email(self, email: str) -> Optional[User]:
113
+ logger.info(f"Looking for user with email: {email}")
114
+ stmt = select(self.user_table).where(self.user_table.email == email)
115
+ result = await self.session.execute(stmt)
116
+ return result.scalar_one_or_none()
117
+
118
+ async def create(self, create_dict: Dict[str, Any]) -> User:
119
+ logger.info(f"Creating new user: {create_dict.get('email')}")
120
+ user = self.user_table(**create_dict)
121
+ self.session.add(user)
122
+ await self.session.commit()
123
+ await self.session.refresh(user)
124
+ return user
125
+
126
+ # دالة لجلب الجلسة async
127
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
128
+ async with AsyncSessionLocal() as session:
129
+ try:
130
+ yield session
131
+ finally:
132
+ await session.close()
133
+
134
+ # دالة لجلب قاعدة بيانات المستخدمين لـ fastapi-users
135
+ async def get_user_db(session: AsyncSession = Depends(get_db)) -> AsyncGenerator[CustomSQLAlchemyUserDatabase, None]:
136
+ yield CustomSQLAlchemyUserDatabase(session, User, OAuthAccount)
137
+
138
+ # دالة لإنشاء الجداول
139
  async def init_db():
140
  try:
141
+ async with async_engine.begin() as conn:
142
+ await conn.run_sync(Base.metadata.create_all)
143
+ logger.info("Database tables created successfully")
144
  except Exception as e:
145
+ logger.error(f"Error creating database tables: {e}")
146
  raise
 
 
 
 
main.py CHANGED
@@ -13,8 +13,8 @@ from starlette.middleware.sessions import SessionMiddleware
13
  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 User, Conversation, get_user_db, init_db
18
  from api.models import UserRead, UserCreate, UserUpdate
19
  from motor.motor_asyncio import AsyncIOMotorClient
20
  from pydantic import BaseModel
@@ -22,6 +22,7 @@ from typing import List
22
  from contextlib import asynccontextmanager
23
  import uvicorn
24
  import markdown2
 
25
  from pathlib import Path
26
  from hashlib import md5
27
  from datetime import datetime
@@ -95,7 +96,7 @@ logger.debug(f"Application settings: QUEUE_SIZE={QUEUE_SIZE}, CONCURRENCY_LIMIT=
95
  # Initialize FastAPI app
96
  @asynccontextmanager
97
  async def lifespan(app: FastAPI):
98
- logger.info("Initializing MongoDB...")
99
  await init_db()
100
  await setup_mongo_index()
101
  yield
@@ -104,8 +105,8 @@ async def lifespan(app: FastAPI):
104
  app = FastAPI(
105
  title="MGZon Chatbot API",
106
  lifespan=lifespan,
107
- docs_url=None,
108
- redoc_url=None
109
  )
110
 
111
  # Add SessionMiddleware
@@ -124,23 +125,23 @@ app.add_middleware(
124
  "https://mgzon-mgzon-app.hf.space",
125
  "http://localhost:7860",
126
  "http://localhost:8000",
127
- "http://localhost",
128
- "https://localhost",
129
- "capacitor://localhost",
130
- "file://",
131
  "https://hager-zon.vercel.app",
132
  "https://mgzon-mgzon-app.hf.space/auth/google/callback",
133
  "https://mgzon-mgzon-app.hf.space/auth/github/callback",
134
  ],
135
  allow_credentials=True,
136
- allow_methods=["*"],
137
- allow_headers=["*"],
138
  )
139
  logger.debug("CORS middleware configured with allowed origins")
140
 
141
  # Include routers
142
  app.include_router(api_router)
143
- get_auth_router(app)
144
  logger.debug("API and auth routers included")
145
 
146
  # Add logout endpoint
@@ -185,6 +186,13 @@ class NotFoundMiddleware(BaseHTTPMiddleware):
185
  {"request": request, "error": "Async context error"},
186
  status_code=500
187
  )
 
 
 
 
 
 
 
188
  return templates.TemplateResponse(
189
  "500.html",
190
  {"request": request, "error": str(e)},
@@ -250,15 +258,19 @@ async def chat_conversation(
250
  request: Request,
251
  conversation_id: str,
252
  user: User = Depends(current_active_user),
 
253
  ):
254
  if not user:
255
  logger.debug("Anonymous user attempted to access conversation page, redirecting to /login")
256
  return RedirectResponse(url="/login", status_code=302)
257
 
258
- conversation = await mongo_db.conversation.find_one({
259
- "conversation_id": conversation_id,
260
- "user_id": user.id
261
- })
 
 
 
262
  if not conversation:
263
  logger.warning(f"Conversation {conversation_id} not found for user {user.email}")
264
  raise HTTPException(status_code=404, detail="Conversation not found")
@@ -269,8 +281,8 @@ async def chat_conversation(
269
  {
270
  "request": request,
271
  "user": user,
272
- "conversation_id": conversation["conversation_id"],
273
- "conversation_title": conversation["title"] or "Untitled Conversation"
274
  }
275
  )
276
 
 
13
  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 # أزل أي ذكر لـ google_oauth_router, github_oauth_router
17
+ from api.database import User, Conversation, get_db, init_db
18
  from api.models import UserRead, UserCreate, UserUpdate
19
  from motor.motor_asyncio import AsyncIOMotorClient
20
  from pydantic import BaseModel
 
22
  from contextlib import asynccontextmanager
23
  import uvicorn
24
  import markdown2
25
+ from sqlalchemy.ext.asyncio import AsyncSession
26
  from pathlib import Path
27
  from hashlib import md5
28
  from datetime import datetime
 
96
  # Initialize FastAPI app
97
  @asynccontextmanager
98
  async def lifespan(app: FastAPI):
99
+ logger.info("Initializing database and MongoDB index...")
100
  await init_db()
101
  await setup_mongo_index()
102
  yield
 
105
  app = FastAPI(
106
  title="MGZon Chatbot API",
107
  lifespan=lifespan,
108
+ docs_url=None, # تعطيل Swagger UI الافتراضي على /docs
109
+ redoc_url=None # تعطيل ReDoc الافتراضي لو مش عايزه
110
  )
111
 
112
  # Add SessionMiddleware
 
125
  "https://mgzon-mgzon-app.hf.space",
126
  "http://localhost:7860",
127
  "http://localhost:8000",
128
+ "http://localhost", # إضافة لدعم Capacitor على Android/iOS
129
+ "https://localhost", # إضافة لدعم HTTPS المحلي
130
+ "capacitor://localhost", # دعم Capacitor native apps
131
+ "file://", # دعم الملفات المحلية في offline mode
132
  "https://hager-zon.vercel.app",
133
  "https://mgzon-mgzon-app.hf.space/auth/google/callback",
134
  "https://mgzon-mgzon-app.hf.space/auth/github/callback",
135
  ],
136
  allow_credentials=True,
137
+ allow_methods=["*"], # سمح بكل الـ methods
138
+ allow_headers=["*"], # سمح بكل الـ headers
139
  )
140
  logger.debug("CORS middleware configured with allowed origins")
141
 
142
  # Include routers
143
  app.include_router(api_router)
144
+ get_auth_router(app) # يضيف الـ custom OAuth endpoints + JWT + register
145
  logger.debug("API and auth routers included")
146
 
147
  # Add logout endpoint
 
186
  {"request": request, "error": "Async context error"},
187
  status_code=500
188
  )
189
+ elif "SQLAlchemyUserDatabase' object has no attribute 'parse_id" in str(e):
190
+ logger.error("JWT error: Missing parse_id in UserDatabase. Check api/database.py configuration.")
191
+ return templates.TemplateResponse(
192
+ "500.html",
193
+ {"request": request, "error": "JWT authentication configuration error"},
194
+ status_code=500
195
+ )
196
  return templates.TemplateResponse(
197
  "500.html",
198
  {"request": request, "error": str(e)},
 
258
  request: Request,
259
  conversation_id: str,
260
  user: User = Depends(current_active_user),
261
+ db: AsyncSession = Depends(get_db)
262
  ):
263
  if not user:
264
  logger.debug("Anonymous user attempted to access conversation page, redirecting to /login")
265
  return RedirectResponse(url="/login", status_code=302)
266
 
267
+ conversation = await db.execute(
268
+ select(Conversation).filter(
269
+ Conversation.conversation_id == conversation_id,
270
+ Conversation.user_id == user.id
271
+ )
272
+ )
273
+ conversation = conversation.scalar_one_or_none()
274
  if not conversation:
275
  logger.warning(f"Conversation {conversation_id} not found for user {user.email}")
276
  raise HTTPException(status_code=404, detail="Conversation not found")
 
281
  {
282
  "request": request,
283
  "user": user,
284
+ "conversation_id": conversation.conversation_id,
285
+ "conversation_title": conversation.title or "Untitled Conversation"
286
  }
287
  )
288
 
requirements.txt CHANGED
@@ -1,11 +1,10 @@
1
  fastapi==0.95.2
2
  fastapi-users[sqlalchemy,oauth2]==10.4.2
3
- fastapi-users-db-mongobeanie==2.0.0
4
- beanie==1.26.0
5
  pydantic==1.10.13
6
  email-validator==1.3.1
7
- motor==3.7.0
8
- pymongo==4.10.1
 
9
  python-jose[cryptography]==3.3.0
10
  passlib[bcrypt]==1.7.4
11
  bcrypt==4.1.3
@@ -35,8 +34,10 @@ urllib3==2.2.3
35
  itsdangerous==2.2.0
36
  protobuf==3.19.6
37
  aiofiles==24.1.0
 
38
  redis==5.0.0
39
  markdown2==2.5.0
 
40
  parler-tts @ git+https://github.com/huggingface/parler-tts.git@5d0aca9753ab74ded179732f5bd797f7a8c6f8ee
41
  soupsieve>=2.5
42
  tqdm>=4.66.0
@@ -48,7 +49,7 @@ librosa>=0.10.0
48
  matplotlib>=3.10.0
49
  accelerate>=0.26.0
50
  diffusers>=0.30.0
51
- psutil>=6.0.0
52
  xformers>=0.0.27
53
  anyio==4.6.0
54
  pyjwt>=2.4.0
 
1
  fastapi==0.95.2
2
  fastapi-users[sqlalchemy,oauth2]==10.4.2
 
 
3
  pydantic==1.10.13
4
  email-validator==1.3.1
5
+ sqlalchemy[asyncio]
6
+ aiosqlite==0.21.0
7
+ sqlalchemy==2.0.43
8
  python-jose[cryptography]==3.3.0
9
  passlib[bcrypt]==1.7.4
10
  bcrypt==4.1.3
 
34
  itsdangerous==2.2.0
35
  protobuf==3.19.6
36
  aiofiles==24.1.0
37
+ motor==3.7.0
38
  redis==5.0.0
39
  markdown2==2.5.0
40
+ pymongo==4.10.1
41
  parler-tts @ git+https://github.com/huggingface/parler-tts.git@5d0aca9753ab74ded179732f5bd797f7a8c6f8ee
42
  soupsieve>=2.5
43
  tqdm>=4.66.0
 
49
  matplotlib>=3.10.0
50
  accelerate>=0.26.0
51
  diffusers>=0.30.0
52
+ psutil>=5.9.0
53
  xformers>=0.0.27
54
  anyio==4.6.0
55
  pyjwt>=2.4.0