pipeline issues fix
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-09-25 11:59:54 +09:00
parent dc50a9858e
commit 4e3768a6ee
39 changed files with 1297 additions and 739 deletions

View File

@@ -0,0 +1 @@
# User Service Package

View File

@@ -1,18 +1,26 @@
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from datetime import timedelta
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from services.user_service.models import User
from services.user_service.schemas import (
Token,
UserCreate,
UserLogin,
UserResponse,
UserUpdate,
)
from shared.auth import (
create_access_token,
get_current_user_from_token,
get_password_hash,
verify_password,
)
from shared.config import settings
from shared.database import get_db
from shared.auth import (
verify_password,
get_password_hash,
create_access_token,
get_current_user_from_token
)
from services.user_service.models import User
from services.user_service.schemas import UserCreate, UserResponse, UserLogin, Token, UserUpdate
app = FastAPI(title="User Service", version="1.0.0")
@@ -28,7 +36,7 @@ app.add_middleware(
async def get_current_user(
user_data: dict = Depends(get_current_user_from_token),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Get current user from token via auth dependency."""
# Get full user object from database
@@ -36,8 +44,7 @@ async def get_current_user(
user = result.scalars().first()
if user is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
)
return user
@@ -55,10 +62,9 @@ async def register_user(user_data: UserCreate, db: AsyncSession = Depends(get_db
result = await db.execute(select(User).filter(User.email == user_data.email))
if result.scalars().first():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
)
# Create new user
hashed_password = get_password_hash(user_data.password)
db_user = User(
@@ -70,11 +76,11 @@ async def register_user(user_data: UserCreate, db: AsyncSession = Depends(get_db
date_of_birth=user_data.date_of_birth,
bio=user_data.bio,
)
db.add(db_user)
await db.commit()
await db.refresh(db_user)
return UserResponse.model_validate(db_user)
@@ -83,25 +89,25 @@ async def login(user_credentials: UserLogin, db: AsyncSession = Depends(get_db))
"""Authenticate user and return token"""
result = await db.execute(select(User).filter(User.email == user_credentials.email))
user = result.scalars().first()
if not user or not verify_password(user_credentials.password, user.password_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Account is deactivated",
)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": str(user.id), "email": user.email},
expires_delta=access_token_expires
data={"sub": str(user.id), "email": user.email},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
@@ -115,17 +121,17 @@ async def get_profile(current_user: User = Depends(get_current_user)):
async def update_profile(
user_update: UserUpdate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Update user profile"""
update_data = user_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(current_user, field, value)
await db.commit()
await db.refresh(current_user)
return UserResponse.model_validate(current_user)
@@ -137,4 +143,5 @@ async def health_check():
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
uvicorn.run(app, host="0.0.0.0", port=8001)

View File

@@ -1,39 +1,41 @@
from sqlalchemy import Column, String, Integer, Date, Text, Boolean
from sqlalchemy.dialects.postgresql import UUID
from shared.database import BaseModel
import uuid
from sqlalchemy import Boolean, Column, Date, Integer, String, Text
from sqlalchemy.dialects.postgresql import UUID
from shared.database import BaseModel
class User(BaseModel):
__tablename__ = "users"
uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
phone = Column(String, unique=True, index=True)
password_hash = Column(String, nullable=False)
# Profile information
first_name = Column(String(50), nullable=False)
last_name = Column(String(50), nullable=False)
date_of_birth = Column(Date)
avatar_url = Column(String)
bio = Column(Text)
# Emergency contacts
emergency_contact_1_name = Column(String(100))
emergency_contact_1_phone = Column(String(20))
emergency_contact_2_name = Column(String(100))
emergency_contact_2_phone = Column(String(20))
# Settings
location_sharing_enabled = Column(Boolean, default=True)
emergency_notifications_enabled = Column(Boolean, default=True)
push_notifications_enabled = Column(Boolean, default=True)
# Security
email_verified = Column(Boolean, default=False)
phone_verified = Column(Boolean, default=False)
is_blocked = Column(Boolean, default=False)
def __repr__(self):
return f"<User {self.email}>"
return f"<User {self.email}>"

View File

@@ -1,8 +1,9 @@
from pydantic import BaseModel, EmailStr, Field, field_validator
from typing import Optional
from datetime import date
from typing import Optional
from uuid import UUID
from pydantic import BaseModel, EmailStr, Field, field_validator
class UserBase(BaseModel):
email: EmailStr
@@ -24,13 +25,13 @@ class UserUpdate(BaseModel):
date_of_birth: Optional[date] = None
bio: Optional[str] = Field(None, max_length=500)
avatar_url: Optional[str] = None
# Emergency contacts
emergency_contact_1_name: Optional[str] = Field(None, max_length=100)
emergency_contact_1_phone: Optional[str] = Field(None, max_length=20)
emergency_contact_2_name: Optional[str] = Field(None, max_length=100)
emergency_contact_2_phone: Optional[str] = Field(None, max_length=20)
# Settings
location_sharing_enabled: Optional[bool] = None
emergency_notifications_enabled: Optional[bool] = None
@@ -51,14 +52,14 @@ class UserResponse(UserBase):
email_verified: bool
phone_verified: bool
is_active: bool
@field_validator('uuid', mode='before')
@field_validator("uuid", mode="before")
@classmethod
def convert_uuid_to_str(cls, v):
if isinstance(v, UUID):
return str(v)
return v
class Config:
from_attributes = True
@@ -74,4 +75,4 @@ class Token(BaseModel):
class TokenData(BaseModel):
email: Optional[str] = None
email: Optional[str] = None