Spaces:
Sleeping
Sleeping
Upload 13 files
Browse files- main.py +33 -0
- requirements.txt +8 -0
- src/core/config.py +20 -0
- src/core/database.py +31 -0
- src/models/mongo/base.py +27 -0
- src/models/mongo/cv_model.py +11 -0
- src/models/mongo/feedback_model.py +11 -0
- src/models/mongo/interview_history_model.py +12 -0
- src/models/postgres/user_model.py +21 -0
- src/services/cv_router.py +40 -0
- src/services/feedback_router.py +40 -0
- src/services/interview_history_router.py +53 -0
- src/services/user_router.py +67 -0
main.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from fastapi import FastAPI
|
3 |
+
from pydantic import BaseModel
|
4 |
+
from src.core.config import settings
|
5 |
+
from src.services.user_router import router as user_router
|
6 |
+
from src.services.cv_router import router as cv_router
|
7 |
+
from src.services.interview_history_router import router as interview_history_router
|
8 |
+
from src.services.feedback_router import router as feedback_router
|
9 |
+
|
10 |
+
app = FastAPI(
|
11 |
+
title="Data Access API",
|
12 |
+
description="API for accessing data from MongoDB and PostgreSQL.",
|
13 |
+
version="1.0.0",
|
14 |
+
docs_url="/docs",
|
15 |
+
redoc_url="/redoc"
|
16 |
+
)
|
17 |
+
|
18 |
+
app.include_router(user_router, prefix="/api/v1", tags=["Users"])
|
19 |
+
app.include_router(cv_router, prefix="/api/v1", tags=["CVs"])
|
20 |
+
app.include_router(interview_history_router, prefix="/api/v1", tags=["Interview Histories"])
|
21 |
+
app.include_router(feedback_router, prefix="/api/v1", tags=["Feedbacks"])
|
22 |
+
|
23 |
+
class HealthCheck(BaseModel):
|
24 |
+
status: str = "ok"
|
25 |
+
|
26 |
+
@app.get("/", response_model=HealthCheck, tags=["Status"])
|
27 |
+
async def health_check():
|
28 |
+
return HealthCheck()
|
29 |
+
|
30 |
+
if __name__ == "__main__":
|
31 |
+
import uvicorn
|
32 |
+
port = int(os.getenv("PORT", 8003)) # Use PORT environment variable, default to 8003
|
33 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi
|
2 |
+
uvicorn[standard]
|
3 |
+
pydantic
|
4 |
+
pydantic-settings
|
5 |
+
motor
|
6 |
+
sqlalchemy
|
7 |
+
psycopg2-binary
|
8 |
+
python-dotenv
|
src/core/config.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic_settings import BaseSettings
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
|
4 |
+
class Settings(BaseSettings):
|
5 |
+
# MongoDB
|
6 |
+
MONGO_URI: str
|
7 |
+
MONGO_DB_NAME: str
|
8 |
+
MONGO_CV_COLLECTION: str
|
9 |
+
MONGO_INTERVIEW_COLLECTION: str
|
10 |
+
MONGO_FEEDBACK_COLLECTION: str
|
11 |
+
|
12 |
+
# PostgreSQL
|
13 |
+
DATABASE_URL: str
|
14 |
+
ASYNC_DATABASE_URL: str
|
15 |
+
|
16 |
+
class Config:
|
17 |
+
env_file = ".env"
|
18 |
+
|
19 |
+
load_dotenv()
|
20 |
+
settings = Settings()
|
src/core/database.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from motor.motor_asyncio import AsyncIOMotorClient
|
2 |
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
3 |
+
from sqlalchemy.orm import sessionmaker
|
4 |
+
from src.core.config import settings
|
5 |
+
from sqlalchemy.pool import NullPool
|
6 |
+
|
7 |
+
# MongoDB client
|
8 |
+
mongo_client = AsyncIOMotorClient(settings.MONGO_URI)
|
9 |
+
mongo_db = mongo_client[settings.MONGO_DB_NAME]
|
10 |
+
|
11 |
+
|
12 |
+
connect_args = {"statement_cache_size": 0}
|
13 |
+
|
14 |
+
engine = create_async_engine(
|
15 |
+
str(settings.ASYNC_DATABASE_URL),
|
16 |
+
poolclass=NullPool,
|
17 |
+
connect_args=connect_args,
|
18 |
+
execution_options={"compiled_cache": None},
|
19 |
+
)
|
20 |
+
|
21 |
+
SessionLocal = sessionmaker(
|
22 |
+
autocommit=False,
|
23 |
+
autoflush=False,
|
24 |
+
bind=engine,
|
25 |
+
class_=AsyncSession,
|
26 |
+
expire_on_commit=False,
|
27 |
+
)
|
28 |
+
|
29 |
+
async def get_db():
|
30 |
+
async with SessionLocal() as session:
|
31 |
+
yield session
|
src/models/mongo/base.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from motor.motor_asyncio import AsyncIOMotorDatabase
|
2 |
+
from pydantic import BaseModel
|
3 |
+
from bson import ObjectId
|
4 |
+
|
5 |
+
class BaseMongoModel(BaseModel):
|
6 |
+
id: str | None = None
|
7 |
+
|
8 |
+
@classmethod
|
9 |
+
async def get(cls, db: AsyncIOMotorDatabase, collection: str, query: dict):
|
10 |
+
return await db[collection].find_one(query)
|
11 |
+
|
12 |
+
@classmethod
|
13 |
+
async def get_all(cls, db: AsyncIOMotorDatabase, collection: str, query: dict = {}):
|
14 |
+
return await db[collection].find(query).to_list(1000)
|
15 |
+
|
16 |
+
@classmethod
|
17 |
+
async def create(cls, db: AsyncIOMotorDatabase, collection: str, data: dict):
|
18 |
+
result = await db[collection].insert_one(data)
|
19 |
+
return str(result.inserted_id)
|
20 |
+
|
21 |
+
@classmethod
|
22 |
+
async def update(cls, db: AsyncIOMotorDatabase, collection: str, query: dict, data: dict):
|
23 |
+
await db[collection].update_one(query, {"$set": data})
|
24 |
+
|
25 |
+
@classmethod
|
26 |
+
async def delete(cls, db: AsyncIOMotorDatabase, collection: str, query: dict):
|
27 |
+
await db[collection].delete_one(query)
|
src/models/mongo/cv_model.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import Field
|
2 |
+
from app.models.mongo.base import BaseMongoModel
|
3 |
+
from app.config import settings
|
4 |
+
|
5 |
+
class CVModel(BaseMongoModel):
|
6 |
+
collection_name: str = settings.MONGO_CV_COLLECTION
|
7 |
+
|
8 |
+
user_id: str | None = None
|
9 |
+
parsed_data: dict = Field(default_factory=dict)
|
10 |
+
raw_text: str | None = None
|
11 |
+
upload_date: str | None = None # ISO format string
|
src/models/mongo/feedback_model.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import Field
|
2 |
+
from app.models.mongo.base import BaseMongoModel
|
3 |
+
from app.config import settings
|
4 |
+
|
5 |
+
class FeedbackModel(BaseMongoModel):
|
6 |
+
collection_name: str = settings.MONGO_FEEDBACK_COLLECTION
|
7 |
+
|
8 |
+
user_id: str | None = None
|
9 |
+
interview_id: str | None = None
|
10 |
+
feedback_content: dict = Field(default_factory=dict)
|
11 |
+
feedback_date: str | None = None # ISO format string
|
src/models/mongo/interview_history_model.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import Field
|
2 |
+
from app.models.mongo.base import BaseMongoModel
|
3 |
+
from app.config import settings
|
4 |
+
|
5 |
+
class InterviewHistoryModel(BaseMongoModel):
|
6 |
+
collection_name: str = settings.MONGO_INTERVIEW_COLLECTION
|
7 |
+
|
8 |
+
user_id: str | None = None
|
9 |
+
cv_id: str | None = None
|
10 |
+
conversation: list[dict] = Field(default_factory=list) # List of {role: str, content: str}
|
11 |
+
start_time: str | None = None # ISO format string
|
12 |
+
end_time: str | None = None # ISO format string
|
src/models/postgres/user_model.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import Column, Integer, String, DateTime, Boolean, JSON
|
2 |
+
from sqlalchemy.ext.declarative import declarative_base
|
3 |
+
from datetime import datetime
|
4 |
+
|
5 |
+
Base = declarative_base()
|
6 |
+
|
7 |
+
class User(Base):
|
8 |
+
__tablename__ = "user"
|
9 |
+
|
10 |
+
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
11 |
+
google_id = Column(String, unique=True, nullable=True, index=True)
|
12 |
+
email = Column(String, unique=True, index=True, nullable=False)
|
13 |
+
name = Column(String, nullable=True)
|
14 |
+
picture_url = Column(String, nullable=True)
|
15 |
+
candidate_mongo_id = Column(String, nullable=True)
|
16 |
+
created_at = Column(DateTime, nullable=True)
|
17 |
+
auth_providers = Column(JSON, default=list)
|
18 |
+
hashed_password = Column(String, nullable=True)
|
19 |
+
is_active = Column(Boolean, default=True)
|
20 |
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
21 |
+
last_login = Column(DateTime, nullable=True)
|
src/services/cv_router.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
2 |
+
from motor.motor_asyncio import AsyncIOMotorDatabase
|
3 |
+
from src.core.database import mongo_db
|
4 |
+
from src.models.mongo.cv_model import CVModel
|
5 |
+
from pydantic import BaseModel
|
6 |
+
from typing import Optional, Dict, Any
|
7 |
+
from bson import ObjectId
|
8 |
+
|
9 |
+
router = APIRouter()
|
10 |
+
|
11 |
+
class CVCreate(BaseModel):
|
12 |
+
user_id: str
|
13 |
+
parsed_data: Dict[str, Any]
|
14 |
+
raw_text: Optional[str] = None
|
15 |
+
upload_date: str
|
16 |
+
|
17 |
+
class CVResponse(BaseModel):
|
18 |
+
id: str = Field(alias="_id")
|
19 |
+
user_id: str
|
20 |
+
parsed_data: Dict[str, Any]
|
21 |
+
raw_text: Optional[str] = None
|
22 |
+
upload_date: str
|
23 |
+
|
24 |
+
class Config:
|
25 |
+
populate_by_name = True
|
26 |
+
json_encoders = {ObjectId: str}
|
27 |
+
|
28 |
+
@router.post("/cvs", response_model=CVResponse)
|
29 |
+
async def create_cv(cv: CVCreate, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
30 |
+
cv_entry = CVModel(**cv.model_dump(by_alias=True))
|
31 |
+
cv_id = await CVModel.create(db, CVModel.collection_name, cv_entry.model_dump(exclude_unset=True))
|
32 |
+
cv_entry.id = cv_id
|
33 |
+
return cv_entry
|
34 |
+
|
35 |
+
@router.get("/cvs/{cv_id}", response_model=CVResponse)
|
36 |
+
async def get_cv_by_id(cv_id: str, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
37 |
+
cv = await CVModel.get(db, CVModel.collection_name, {"_id": cv_id})
|
38 |
+
if cv is None:
|
39 |
+
raise HTTPException(status_code=404, detail="CV not found")
|
40 |
+
return cv
|
src/services/feedback_router.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
2 |
+
from motor.motor_asyncio import AsyncIOMotorDatabase
|
3 |
+
from src.core.database import mongo_db
|
4 |
+
from src.models.mongo.feedback_model import FeedbackModel
|
5 |
+
from pydantic import BaseModel, Field
|
6 |
+
from typing import Optional, Dict, Any
|
7 |
+
from bson import ObjectId
|
8 |
+
|
9 |
+
router = APIRouter()
|
10 |
+
|
11 |
+
class FeedbackCreate(BaseModel):
|
12 |
+
user_id: str
|
13 |
+
interview_id: str
|
14 |
+
feedback_content: Dict[str, Any]
|
15 |
+
feedback_date: str
|
16 |
+
|
17 |
+
class FeedbackResponse(BaseModel):
|
18 |
+
id: str = Field(alias="_id")
|
19 |
+
user_id: str
|
20 |
+
interview_id: str
|
21 |
+
feedback_content: Dict[str, Any]
|
22 |
+
feedback_date: str
|
23 |
+
|
24 |
+
class Config:
|
25 |
+
populate_by_name = True
|
26 |
+
json_encoders = {ObjectId: str}
|
27 |
+
|
28 |
+
@router.post("/feedbacks", response_model=FeedbackResponse)
|
29 |
+
async def create_feedback(feedback: FeedbackCreate, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
30 |
+
feedback_entry = FeedbackModel(**feedback.model_dump(by_alias=True))
|
31 |
+
feedback_id = await FeedbackModel.create(db, FeedbackModel.collection_name, feedback_entry.model_dump(exclude_unset=True))
|
32 |
+
feedback_entry.id = feedback_id
|
33 |
+
return feedback_entry
|
34 |
+
|
35 |
+
@router.get("/feedbacks/{feedback_id}", response_model=FeedbackResponse)
|
36 |
+
async def get_feedback_by_id(feedback_id: str, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
37 |
+
feedback = await FeedbackModel.get(db, FeedbackModel.collection_name, {"_id": feedback_id})
|
38 |
+
if feedback is None:
|
39 |
+
raise HTTPException(status_code=404, detail="Feedback not found")
|
40 |
+
return feedback
|
src/services/interview_history_router.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
2 |
+
from motor.motor_asyncio import AsyncIOMotorDatabase
|
3 |
+
from src.core.database import mongo_db
|
4 |
+
from src.models.mongo.interview_history_model import InterviewHistoryModel
|
5 |
+
from pydantic import BaseModel, Field
|
6 |
+
from typing import Optional, List, Dict, Any
|
7 |
+
from bson import ObjectId
|
8 |
+
|
9 |
+
router = APIRouter()
|
10 |
+
|
11 |
+
class InterviewHistoryCreate(BaseModel):
|
12 |
+
user_id: str
|
13 |
+
cv_id: str
|
14 |
+
job_offer_id: str
|
15 |
+
conversation: List[Dict[str, Any]]
|
16 |
+
start_time: str
|
17 |
+
end_time: Optional[str] = None
|
18 |
+
|
19 |
+
class InterviewHistoryUpdate(BaseModel):
|
20 |
+
conversation: Optional[List[Dict[str, Any]]] = None
|
21 |
+
end_time: Optional[str] = None
|
22 |
+
|
23 |
+
class InterviewHistoryResponse(BaseModel):
|
24 |
+
id: str = Field(alias="_id")
|
25 |
+
user_id: str
|
26 |
+
cv_id: str
|
27 |
+
job_offer_id: str
|
28 |
+
conversation: List[Dict[str, Any]]
|
29 |
+
start_time: str
|
30 |
+
end_time: Optional[str] = None
|
31 |
+
|
32 |
+
class Config:
|
33 |
+
populate_by_name = True
|
34 |
+
json_encoders = {ObjectId: str}
|
35 |
+
|
36 |
+
@router.post("/interview-histories", response_model=InterviewHistoryResponse)
|
37 |
+
async def create_interview_history(history: InterviewHistoryCreate, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
38 |
+
history_entry = InterviewHistoryModel(**history.model_dump(by_alias=True))
|
39 |
+
history_id = await InterviewHistoryModel.create(db, InterviewHistoryModel.collection_name, history_entry.model_dump(exclude_unset=True))
|
40 |
+
history_entry.id = history_id
|
41 |
+
return history_entry
|
42 |
+
|
43 |
+
@router.get("/interview-histories/{history_id}", response_model=InterviewHistoryResponse)
|
44 |
+
async def get_interview_history_by_id(history_id: str, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
45 |
+
history = await InterviewHistoryModel.get(db, InterviewHistoryModel.collection_name, {"_id": history_id})
|
46 |
+
if history is None:
|
47 |
+
raise HTTPException(status_code=404, detail="Interview history not found")
|
48 |
+
return history
|
49 |
+
|
50 |
+
@router.put("/interview-histories/{history_id}", response_model=InterviewHistoryResponse)
|
51 |
+
async def update_interview_history(history_id: str, history: InterviewHistoryUpdate, db: AsyncIOMotorDatabase = Depends(lambda: mongo_db)):
|
52 |
+
await InterviewHistoryModel.update(db, InterviewHistoryModel.collection_name, {"_id": history_id}, history.model_dump(exclude_unset=True))
|
53 |
+
return await get_interview_history_by_id(history_id, db)
|
src/services/user_router.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException
|
2 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
3 |
+
from sqlalchemy import select, update
|
4 |
+
from src.core.database import get_db
|
5 |
+
from src.models.postgres.user_model import User
|
6 |
+
from pydantic import BaseModel
|
7 |
+
from typing import Optional
|
8 |
+
|
9 |
+
router = APIRouter()
|
10 |
+
|
11 |
+
class UserCreate(BaseModel):
|
12 |
+
email: str
|
13 |
+
hashed_password: str
|
14 |
+
name: Optional[str] = None
|
15 |
+
picture_url: Optional[str] = None
|
16 |
+
google_id: Optional[str] = None
|
17 |
+
candidate_mongo_id: Optional[str] = None
|
18 |
+
|
19 |
+
class UserUpdate(BaseModel):
|
20 |
+
email: Optional[str] = None
|
21 |
+
hashed_password: Optional[str] = None
|
22 |
+
name: Optional[str] = None
|
23 |
+
picture_url: Optional[str] = None
|
24 |
+
google_id: Optional[str] = None
|
25 |
+
candidate_mongo_id: Optional[str] = None
|
26 |
+
|
27 |
+
class UserResponse(BaseModel):
|
28 |
+
id: int
|
29 |
+
email: str
|
30 |
+
name: Optional[str] = None
|
31 |
+
picture_url: Optional[str] = None
|
32 |
+
google_id: Optional[str] = None
|
33 |
+
candidate_mongo_id: Optional[str] = None
|
34 |
+
|
35 |
+
class Config:
|
36 |
+
from_attributes = True
|
37 |
+
|
38 |
+
@router.post("/users", response_model=UserResponse)
|
39 |
+
async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db)):
|
40 |
+
db_user = User(**user.model_dump())
|
41 |
+
db.add(db_user)
|
42 |
+
await db.commit()
|
43 |
+
await db.refresh(db_user)
|
44 |
+
return db_user
|
45 |
+
|
46 |
+
@router.get("/users/{user_id}", response_model=UserResponse)
|
47 |
+
async def get_user_by_id(user_id: int, db: AsyncSession = Depends(get_db)):
|
48 |
+
result = await db.execute(select(User).where(User.id == user_id))
|
49 |
+
user = result.scalar_one_or_none()
|
50 |
+
if user is None:
|
51 |
+
raise HTTPException(status_code=404, detail="User not found")
|
52 |
+
return user
|
53 |
+
|
54 |
+
@router.get("/users/email/{email}", response_model=UserResponse)
|
55 |
+
async def get_user_by_email(email: str, db: AsyncSession = Depends(get_db)):
|
56 |
+
result = await db.execute(select(User).where(User.email == email))
|
57 |
+
user = result.scalar_one_or_none()
|
58 |
+
if user is None:
|
59 |
+
raise HTTPException(status_code=404, detail="User not found")
|
60 |
+
return user
|
61 |
+
|
62 |
+
@router.put("/users/{user_id}", response_model=UserResponse)
|
63 |
+
async def update_user(user_id: int, user: UserUpdate, db: AsyncSession = Depends(get_db)):
|
64 |
+
query = update(User).where(User.id == user_id).values(**user.model_dump(exclude_unset=True))
|
65 |
+
await db.execute(query)
|
66 |
+
await db.commit()
|
67 |
+
return await get_user_by_id(user_id, db)
|