Mark-Lasfar
commited on
Commit
·
8c49d21
1
Parent(s):
7f03ffe
Fix circular import by moving CustomSQLAlchemyUserDatabase to database.py and removing user_db.py
Browse files- api/auth.py +2 -3
- api/database.py +32 -8
- api/user_db.py +0 -51
- init_db.py +5 -1
api/auth.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# api/auth.py
|
| 2 |
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
-
# SPDX-License-
|
| 4 |
|
| 5 |
from fastapi_users import FastAPIUsers
|
| 6 |
from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
|
|
@@ -17,8 +17,7 @@ import os
|
|
| 17 |
import logging
|
| 18 |
import secrets
|
| 19 |
|
| 20 |
-
from api.
|
| 21 |
-
from api.database import User, OAuthAccount
|
| 22 |
from api.models import UserRead, UserCreate, UserUpdate
|
| 23 |
|
| 24 |
# إعداد اللوقينج
|
|
|
|
| 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
|
|
|
|
| 17 |
import logging
|
| 18 |
import secrets
|
| 19 |
|
| 20 |
+
from api.database import User, OAuthAccount, CustomSQLAlchemyUserDatabase, get_user_db # استيراد من database.py
|
|
|
|
| 21 |
from api.models import UserRead, UserCreate, UserUpdate
|
| 22 |
|
| 23 |
# إعداد اللوقينج
|
api/database.py
CHANGED
|
@@ -1,29 +1,29 @@
|
|
| 1 |
# api/database.py
|
| 2 |
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
-
# SPDX-License-
|
| 4 |
|
| 5 |
import os
|
| 6 |
import logging
|
| 7 |
from datetime import datetime
|
| 8 |
-
from typing import AsyncGenerator
|
| 9 |
|
| 10 |
-
from sqlalchemy import Column, String, Integer, ForeignKey, DateTime, Boolean, Text
|
| 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 |
import aiosqlite
|
| 16 |
-
from api.user_db import CustomSQLAlchemyUserDatabase, get_user_db # استيراد من user_db.py
|
| 17 |
|
| 18 |
# إعداد اللوج
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
| 21 |
-
#
|
| 22 |
SQLALCHEMY_DATABASE_URL = os.environ.get(
|
| 23 |
"SQLALCHEMY_DATABASE_URL"
|
| 24 |
) or "sqlite+aiosqlite:///./data/mgzon_users.db"
|
| 25 |
|
| 26 |
-
#
|
| 27 |
if "aiosqlite" not in SQLALCHEMY_DATABASE_URL:
|
| 28 |
raise ValueError("Database URL must use 'sqlite+aiosqlite' for async support")
|
| 29 |
|
|
@@ -58,7 +58,7 @@ class OAuthAccount(Base):
|
|
| 58 |
|
| 59 |
user = relationship("User", back_populates="oauth_accounts", lazy="selectin")
|
| 60 |
|
| 61 |
-
class User(Base):
|
| 62 |
__tablename__ = "user"
|
| 63 |
id = Column(Integer, primary_key=True, index=True)
|
| 64 |
email = Column(String, unique=True, index=True, nullable=False)
|
|
@@ -99,6 +99,30 @@ class Message(Base):
|
|
| 99 |
|
| 100 |
conversation = relationship("Conversation", back_populates="messages")
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
# دالة لجلب الجلسة async
|
| 103 |
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
| 104 |
async with AsyncSessionLocal() as session:
|
|
@@ -111,7 +135,7 @@ async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
| 111 |
async def get_user_db(session: AsyncSession = Depends(get_db)) -> AsyncGenerator[CustomSQLAlchemyUserDatabase, None]:
|
| 112 |
yield CustomSQLAlchemyUserDatabase(session, User, OAuthAccount)
|
| 113 |
|
| 114 |
-
#
|
| 115 |
async def init_db():
|
| 116 |
try:
|
| 117 |
async with async_engine.begin() as conn:
|
|
|
|
| 1 |
# api/database.py
|
| 2 |
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
# SPDX-License-License: Apache-2.0
|
| 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 SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
|
| 16 |
import aiosqlite
|
|
|
|
| 17 |
|
| 18 |
# إعداد اللوج
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
| 21 |
+
# استخدم القيمة مباشرة إذا لم يكن هناك متغير بيئة
|
| 22 |
SQLALCHEMY_DATABASE_URL = os.environ.get(
|
| 23 |
"SQLALCHEMY_DATABASE_URL"
|
| 24 |
) or "sqlite+aiosqlite:///./data/mgzon_users.db"
|
| 25 |
|
| 26 |
+
# تأكد أن الدرايفر async
|
| 27 |
if "aiosqlite" not in SQLALCHEMY_DATABASE_URL:
|
| 28 |
raise ValueError("Database URL must use 'sqlite+aiosqlite' for async support")
|
| 29 |
|
|
|
|
| 58 |
|
| 59 |
user = relationship("User", back_populates="oauth_accounts", lazy="selectin")
|
| 60 |
|
| 61 |
+
class User(SQLAlchemyBaseUserTable[int], Base):
|
| 62 |
__tablename__ = "user"
|
| 63 |
id = Column(Integer, primary_key=True, index=True)
|
| 64 |
email = Column(String, unique=True, index=True, nullable=False)
|
|
|
|
| 99 |
|
| 100 |
conversation = relationship("Conversation", back_populates="messages")
|
| 101 |
|
| 102 |
+
# قاعدة بيانات المستخدم المخصصة (نقلناها من user_db.py)
|
| 103 |
+
class CustomSQLAlchemyUserDatabase(SQLAlchemyUserDatabase[User, int]):
|
| 104 |
+
"""
|
| 105 |
+
قاعدة بيانات مخصَّصة لمكتبة fastapi-users.
|
| 106 |
+
تضيف طريقة parse_id التي تُحوِّل الـ ID من str → int.
|
| 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:
|
|
|
|
| 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:
|
api/user_db.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
| 1 |
-
# api/user_db.py
|
| 2 |
-
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
-
# SPDX-License-Identifier: Apache-2.0
|
| 4 |
-
|
| 5 |
-
import logging
|
| 6 |
-
from typing import Any, AsyncGenerator, Dict, Optional
|
| 7 |
-
|
| 8 |
-
from fastapi import Depends
|
| 9 |
-
from fastapi_users.db import SQLAlchemyUserDatabase
|
| 10 |
-
from sqlalchemy.ext.asyncio import AsyncSession
|
| 11 |
-
from sqlalchemy import select
|
| 12 |
-
|
| 13 |
-
from api.database import User, OAuthAccount # استيراد جداولك
|
| 14 |
-
|
| 15 |
-
logger = logging.getLogger(__name__)
|
| 16 |
-
|
| 17 |
-
class CustomSQLAlchemyUserDatabase(SQLAlchemyUserDatabase[User, int]):
|
| 18 |
-
"""
|
| 19 |
-
قاعدة بيانات مخصَّصة لمكتبة fastapi‑users.
|
| 20 |
-
تضيف طريقة parse_id التي تُحوِّل الـ ID من str → int.
|
| 21 |
-
"""
|
| 22 |
-
|
| 23 |
-
def parse_id(self, value: Any) -> int:
|
| 24 |
-
logger.debug(f"Parsing user id: {value} (type={type(value)})")
|
| 25 |
-
# إذا كان الـ ID نصًا (من JWT) → حوّله إلى int
|
| 26 |
-
return int(value) if isinstance(value, str) else value
|
| 27 |
-
|
| 28 |
-
# ---------- وظائف مساعدة ----------
|
| 29 |
-
async def get_by_email(self, email: str) -> Optional[User]:
|
| 30 |
-
logger.info(f"Looking for user with email: {email}")
|
| 31 |
-
stmt = select(self.user_table).where(self.user_table.email == email)
|
| 32 |
-
result = await self.session.execute(stmt)
|
| 33 |
-
return result.scalar_one_or_none()
|
| 34 |
-
|
| 35 |
-
async def create(self, create_dict: Dict[str, Any]) -> User:
|
| 36 |
-
logger.info(f"Creating new user: {create_dict.get('email')}")
|
| 37 |
-
user = self.user_table(**create_dict)
|
| 38 |
-
self.session.add(user)
|
| 39 |
-
await self.session.commit()
|
| 40 |
-
await self.session.refresh(user)
|
| 41 |
-
return user
|
| 42 |
-
|
| 43 |
-
# ---------- Dependency يُستَخدم في باقي المشروع ----------
|
| 44 |
-
async def get_user_db(
|
| 45 |
-
session: AsyncSession = Depends(lambda: None) # سيتم استبداله في database.py
|
| 46 |
-
) -> AsyncGenerator[CustomSQLAlchemyUserDatabase, None]:
|
| 47 |
-
"""
|
| 48 |
-
يُستَخدم كـ Depends في جميع المسارات التي تحتاج إلى قاعدة بيانات المستخدم.
|
| 49 |
-
سيتم تمرير الـ AsyncSession الفعلي من `api/database.py`.
|
| 50 |
-
"""
|
| 51 |
-
yield CustomSQLAlchemyUserDatabase(session, User, OAuthAccount)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init_db.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import logging
|
| 3 |
import asyncio
|
|
@@ -6,7 +10,7 @@ from sqlalchemy import select, delete
|
|
| 6 |
from api.database import async_engine, Base, User, OAuthAccount, Conversation, Message, AsyncSessionLocal
|
| 7 |
from passlib.context import CryptContext
|
| 8 |
|
| 9 |
-
#
|
| 10 |
logging.basicConfig(level=logging.INFO)
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
|
|
|
| 1 |
+
# init_db.py
|
| 2 |
+
# SPDX-FileCopyrightText: Hadad <[email protected]>
|
| 3 |
+
# SPDX-License-License: Apache-2.0
|
| 4 |
+
|
| 5 |
import os
|
| 6 |
import logging
|
| 7 |
import asyncio
|
|
|
|
| 10 |
from api.database import async_engine, Base, User, OAuthAccount, Conversation, Message, AsyncSessionLocal
|
| 11 |
from passlib.context import CryptContext
|
| 12 |
|
| 13 |
+
# إعداد اللوج
|
| 14 |
logging.basicConfig(level=logging.INFO)
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|