Skip to content

Commit 7ca7ed2

Browse files
committed
style: apply Black code formatting
1 parent 82c3ebb commit 7ca7ed2

13 files changed

Lines changed: 552 additions & 663 deletions

backend/api/auth.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from fastapi.security import HTTPBearer
88

99
from models.response_schemas import ApiResponse, AuthResponse, LoginRequest, User
10-
from models.user import UserPublic
10+
from models.user import UserInDB
1111
from services.auth_service import AuthService
1212

1313
# Configure logging
@@ -38,17 +38,14 @@ async def login_with_google(request: LoginRequest) -> ApiResponse[AuthResponse]:
3838
request.google_token.strip()
3939
)
4040

41-
# Convert UserInDB to UserPublic for API response
42-
public_user = UserPublic.from_db_user(user)
43-
44-
# Convert to response format expected by frontend
41+
# Convert UserInDB to the response model directly
4542
user_response = User(
46-
id=public_user.id,
47-
email=public_user.email,
48-
name=public_user.name,
49-
avatar_url=public_user.avatar_url,
50-
created_at=public_user.created_at,
51-
last_sign_in_at=public_user.last_sign_in_at,
43+
id=str(user.id),
44+
email=user.email,
45+
name=user.name,
46+
avatar_url=user.avatar_url,
47+
created_at=user.created_at.isoformat(),
48+
last_sign_in_at=user.updated_at.isoformat(), # Using updated_at for last sign-in
5249
)
5350

5451
auth_response = AuthResponse(
@@ -71,6 +68,9 @@ async def login_with_google(request: LoginRequest) -> ApiResponse[AuthResponse]:
7168
),
7269
)
7370

71+
except HTTPException:
72+
# Re-raise HTTPException without modification
73+
raise
7474
except ValueError as e:
7575
logger.error(f"Google OAuth validation error: {str(e)}")
7676
raise HTTPException(status_code=401, detail=f"Invalid Google token: {str(e)}")
@@ -88,15 +88,15 @@ async def get_current_user(
8888
logger.info("Received current user request")
8989

9090
user = auth_service.get_current_user(token)
91-
public_user = UserPublic.from_db_user(user)
9291

92+
# Convert UserInDB to the response model directly
9393
user_response = User(
94-
id=public_user.id,
95-
email=public_user.email,
96-
name=public_user.name,
97-
avatar_url=public_user.avatar_url,
98-
created_at=public_user.created_at,
99-
last_sign_in_at=public_user.last_sign_in_at,
94+
id=str(user.id),
95+
email=user.email,
96+
name=user.name,
97+
avatar_url=user.avatar_url,
98+
created_at=user.created_at.isoformat(),
99+
last_sign_in_at=user.updated_at.isoformat(), # Using updated_at for last sign-in
100100
)
101101

102102
logger.info(f"Current user request successful for: {user.email}")
@@ -164,14 +164,13 @@ async def refresh_token(request: dict) -> ApiResponse[AuthResponse]:
164164
)
165165

166166
# Convert to response format
167-
public_user = UserPublic.from_db_user(user)
168167
user_response = User(
169-
id=public_user.id,
170-
email=public_user.email,
171-
name=public_user.name,
172-
avatar_url=public_user.avatar_url,
173-
created_at=public_user.created_at,
174-
last_sign_in_at=public_user.last_sign_in_at,
168+
id=str(user.id),
169+
email=user.email,
170+
name=user.name,
171+
avatar_url=user.avatar_url,
172+
created_at=user.created_at.isoformat(),
173+
last_sign_in_at=user.updated_at.isoformat(),
175174
)
176175

177176
auth_response = AuthResponse(
@@ -186,6 +185,9 @@ async def refresh_token(request: dict) -> ApiResponse[AuthResponse]:
186185
success=True, data=auth_response, message="Token refreshed successfully"
187186
)
188187

188+
except HTTPException:
189+
# Re-raise HTTPException without modification
190+
raise
189191
except jwt.InvalidTokenError as e:
190192
logger.warning(f"Invalid refresh token: {str(e)}")
191193
raise HTTPException(
@@ -219,6 +221,9 @@ async def auth_health_check() -> ApiResponse[dict]:
219221
detail=f"Authentication service is unhealthy: {health_data.get('error', 'Unknown error')}",
220222
)
221223

224+
except HTTPException:
225+
# Re-raise HTTPException without modification
226+
raise
222227
except Exception as e:
223228
logger.error(f"Auth health check error: {str(e)}")
224229
raise HTTPException(status_code=500, detail=f"Health check failed: {str(e)}")

backend/api/health.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from fastapi import APIRouter
66

7-
from services.database_service import db_service
7+
from services.database_service import get_db_service
88
from services.redis_service import redis_service
99
from services.storage_service import storage_service
1010

@@ -44,7 +44,7 @@ async def health_check() -> Dict[str, Any]:
4444
}
4545

4646
# Check all services in production
47-
database_health = db_service.health_check()
47+
database_health = get_db_service().health_check()
4848
redis_health = redis_service.health_check()
4949
storage_health = storage_service.health_check()
5050

backend/models/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from sqlalchemy.orm import declarative_base
2+
3+
Base = declarative_base()

backend/models/user.py

Lines changed: 90 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,139 @@
11
import uuid
22
from datetime import datetime
3-
from typing import Optional
3+
from typing import Optional, List
44

55
from pydantic import BaseModel, EmailStr, Field, field_validator
6-
from sqlalchemy import Boolean, Column, DateTime, String, Text
7-
from sqlalchemy.dialects.postgresql import UUID
8-
from sqlalchemy.orm import declarative_base, relationship
9-
10-
Base = declarative_base()
6+
from sqlalchemy import Boolean, Column, DateTime, String, Text, func, TypeDecorator
7+
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
8+
from sqlalchemy.orm import Mapped, mapped_column, relationship, declarative_base
9+
10+
from models.base import Base
11+
12+
13+
class UUID(TypeDecorator):
14+
"""
15+
Platform-independent UUID type.
16+
17+
Uses PostgreSQL's UUID type, otherwise uses
18+
CHAR(32), storing as string.
19+
"""
20+
21+
impl = PG_UUID
22+
cache_ok = True
23+
24+
def load_dialect_impl(self, dialect):
25+
if dialect.name == "postgresql":
26+
return dialect.type_descriptor(PG_UUID())
27+
else:
28+
return dialect.type_descriptor(String(32))
29+
30+
def process_bind_param(self, value, dialect):
31+
if value is None:
32+
return value
33+
elif dialect.name == "postgresql":
34+
return str(value)
35+
else:
36+
if not isinstance(value, uuid.UUID):
37+
return "%.32x" % uuid.UUID(value).int
38+
else:
39+
# hexstring
40+
return "%.32x" % value.int
41+
42+
def process_result_value(self, value, dialect):
43+
if value is None:
44+
return value
45+
else:
46+
if not isinstance(value, uuid.UUID):
47+
value = uuid.UUID(value)
48+
return value
1149

1250

1351
class UserTable(Base):
1452
"""SQLAlchemy User table model for PostgreSQL"""
1553

1654
__tablename__ = "users"
1755

18-
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
56+
id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4)
1957
email = Column(String(255), unique=True, nullable=False, index=True)
20-
name = Column(String(255), nullable=False)
58+
name: Mapped[str] = mapped_column(String(255), nullable=False)
2159
avatar_url = Column(Text, nullable=True)
2260
google_id = Column(String(255), unique=True, nullable=True, index=True)
23-
is_active = Column(Boolean, default=True, nullable=False)
24-
is_verified = Column(Boolean, default=False, nullable=False)
25-
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
26-
updated_at = Column(
27-
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
61+
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
62+
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
63+
64+
# Timestamps
65+
created_at: Mapped[datetime] = mapped_column(
66+
DateTime(timezone=True), server_default=func.now()
67+
)
68+
updated_at: Mapped[datetime] = mapped_column(
69+
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
2870
)
29-
last_sign_in_at = Column(DateTime, nullable=True)
3071

3172
# Relationships
32-
# projects = relationship(
33-
# "ProjectTable", back_populates="user", cascade="all, delete"
73+
# projects: Mapped[List["ProjectTable"]] = relationship(
74+
# back_populates="user", cascade="all, delete-orphan"
3475
# )
35-
# chat_messages = relationship(
36-
# "ChatMessageTable", back_populates="user", cascade="all, delete"
76+
# chat_messages: Mapped[List["ChatMessageTable"]] = relationship(
77+
# back_populates="user", cascade="all, delete-orphan"
3778
# )
3879

3980
def __repr__(self):
40-
return f"<User(id={self.id}, email={self.email}, name={self.name})>"
41-
81+
return f"<User(id={self.id}, email='{self.email}', name='{self.name}')>"
4282

43-
class UserCreate(BaseModel):
44-
"""Pydantic model for creating a user"""
4583

46-
email: EmailStr = Field(..., description="User email address")
47-
name: str = Field(..., min_length=1, max_length=255, description="User full name")
48-
avatar_url: Optional[str] = Field(None, description="User avatar URL")
49-
google_id: Optional[str] = Field(None, description="Google OAuth ID")
84+
# Pydantic models for API validation and serialization
5085

51-
@field_validator("name")
52-
@classmethod
53-
def validate_name(cls, v):
54-
if not v or not v.strip():
55-
raise ValueError("Name cannot be empty or just whitespace")
56-
return v.strip()
5786

58-
@field_validator("avatar_url")
59-
@classmethod
60-
def validate_avatar_url(cls, v):
61-
if v and not v.startswith(("http://", "https://")):
62-
raise ValueError("Avatar URL must be a valid HTTP/HTTPS URL")
63-
return v
87+
class UserBase(BaseModel):
88+
email: EmailStr
89+
name: Optional[str] = None
90+
avatar_url: Optional[str] = None
91+
is_active: bool = True
92+
is_verified: bool = False
6493

94+
class Config:
95+
from_attributes = True
6596

66-
class UserUpdate(BaseModel):
67-
"""Pydantic model for updating a user"""
6897

69-
name: Optional[str] = Field(None, min_length=1, max_length=255)
70-
avatar_url: Optional[str] = Field(None)
71-
is_active: Optional[bool] = Field(None)
72-
is_verified: Optional[bool] = Field(None)
73-
last_sign_in_at: Optional[datetime] = Field(None)
98+
class UserCreate(UserBase):
99+
google_id: str
100+
name: str # Make name required for UserCreate
74101

75-
@field_validator("name")
102+
@field_validator("name", "google_id")
76103
@classmethod
77-
def validate_name(cls, v):
78-
if v is not None and (not v or not v.strip()):
79-
raise ValueError("Name cannot be empty or just whitespace")
80-
return v.strip() if v else v
104+
def validate_non_empty(cls, v):
105+
if not v or not v.strip():
106+
raise ValueError("Field cannot be empty")
107+
return v.strip()
81108

82-
@field_validator("avatar_url")
83-
@classmethod
84-
def validate_avatar_url(cls, v):
85-
if v and not v.startswith(("http://", "https://")):
86-
raise ValueError("Avatar URL must be a valid HTTP/HTTPS URL")
87-
return v
88109

110+
class UserUpdate(BaseModel):
111+
name: Optional[str] = None
112+
avatar_url: Optional[str] = None
89113

90-
class UserInDB(BaseModel):
91-
"""Pydantic model for user data from database"""
92114

115+
class UserInDB(UserBase):
93116
id: uuid.UUID
94-
email: str
95-
name: str
96-
avatar_url: Optional[str] = None
97-
google_id: Optional[str] = None
98-
is_active: bool
99-
is_verified: bool
100117
created_at: datetime
101118
updated_at: datetime
102-
last_sign_in_at: Optional[datetime] = None
103-
104-
model_config = {"from_attributes": True}
105-
106119

107-
class UserPublic(BaseModel):
108-
"""Pydantic model for public user data (API responses)"""
109-
110-
id: str
111-
email: str
112-
name: str
113-
avatar_url: Optional[str] = None
114-
created_at: str
115-
last_sign_in_at: Optional[str] = None
116-
117-
@classmethod
118-
def from_db_user(cls, user: UserInDB) -> "UserPublic":
119-
"""Convert database user to public user model"""
120-
return cls(
121-
id=str(user.id),
122-
email=user.email,
123-
name=user.name,
124-
avatar_url=user.avatar_url,
125-
created_at=user.created_at.isoformat() + "Z",
126-
last_sign_in_at=(
127-
user.last_sign_in_at.isoformat() + "Z" if user.last_sign_in_at else None
128-
),
129-
)
120+
class Config:
121+
from_attributes = True
130122

131123

132124
class GoogleOAuthData(BaseModel):
133-
"""Pydantic model for Google OAuth data"""
134-
135125
google_id: str
136126
email: EmailStr
137127
name: str
138128
avatar_url: Optional[str] = None
139-
email_verified: bool = False
129+
email_verified: bool = True
140130

141-
@field_validator("google_id")
131+
@field_validator("name", "google_id", "email")
142132
@classmethod
143-
def validate_google_id(cls, v):
133+
def strip_whitespace(cls, v):
144134
if not v or not v.strip():
145-
raise ValueError("Google ID cannot be empty")
135+
raise ValueError("Field cannot be empty")
146136
return v.strip()
137+
138+
class Config:
139+
from_attributes = True

0 commit comments

Comments
 (0)