-
Notifications
You must be signed in to change notification settings - Fork 264
Solution #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Solution #266
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,86 @@ | ||
| from fastapi import APIRouter | ||
| from datetime import date | ||
| from fastapi import APIRouter, Depends, HTTPException, status, File, UploadFile, Form | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
| from sqlalchemy import select | ||
|
|
||
| from database import get_db, UserProfileModel | ||
| from database.models.accounts import GenderEnum | ||
| from schemas.profiles import ProfileResponseSchema, ProfileCreateSchema | ||
| from validation import validate_image | ||
| from security.http import get_token | ||
| from config.dependencies import get_jwt_auth_manager, get_s3_storage_client | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| # Write your code here | ||
| @router.post("/", | ||
| response_model=ProfileResponseSchema, | ||
| status_code=status.HTTP_201_CREATED, | ||
| summary="Create User profile", | ||
| description="Створює профіль користувача, завантажує аватар у MinIO та зберігає метадані в БД." | ||
| ) | ||
| async def create_profile( | ||
| first_name: str = Form(...), | ||
| last_name: str = Form(...), | ||
| gender: GenderEnum = Form(...), | ||
| date_of_birth: date = Form(...), | ||
| info: str = Form(...), | ||
| avatar: UploadFile = File(...), | ||
| db: AsyncSession = Depends(get_db), | ||
| token: str = Depends(get_token), | ||
| jwt_manager = Depends(get_jwt_auth_manager), | ||
| s3_client = Depends(get_s3_storage_client) | ||
| ): | ||
| try: | ||
| payload = jwt_manager.decode_access_token(token) | ||
| user_id = payload.get("user_id") | ||
| if not user_id: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_401_UNAUTHORIZED, | ||
| detail="User ID not found in token", | ||
| ) | ||
| except Exception: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_401_UNAUTHORIZED, | ||
| detail="Invalid or expired token", | ||
| ) | ||
|
Comment on lines
+42
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This exception handling is too broad. It catches any error during token decoding and returns a generic message. The requirements specify different error details for different token validation failures, such as |
||
|
|
||
| validate_image(avatar) | ||
|
||
|
|
||
| stmt = select(UserProfileModel).where(UserProfileModel.user_id == user_id) | ||
| result = await db.execute(stmt) | ||
| if result.scalars().first(): | ||
| raise HTTPException( | ||
| status_code=status.HTTP_400_BAD_REQUEST, | ||
| detail="Profile already exists for this user.", | ||
|
||
| ) | ||
|
|
||
| object_key = f"avatars/{user_id}_{avatar.filename}" | ||
|
|
||
| try: | ||
| avatar_url = await s3_client.upload_file(avatar, object_key) | ||
| except Exception as e: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail=f"Failed to upload image to storage: {str(e)}" | ||
|
||
| ) | ||
|
|
||
| try: | ||
| new_profile = UserProfileModel( | ||
| user_id=user_id, | ||
| first_name=first_name, | ||
| last_name=last_name, | ||
| gender=gender, | ||
| date_of_birth=date_of_birth, | ||
| info=info, | ||
| avatar=avatar_url, | ||
| ) | ||
| db.add(new_profile) | ||
| await db.commit() | ||
| await db.refresh(new_profile) | ||
| return new_profile | ||
| except Exception: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
| await db.rollback() | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="An error occurred while saving the profile to the database." | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,57 @@ | ||
| from datetime import date | ||
| from typing import Optional | ||
|
|
||
| from fastapi import UploadFile, Form, File, HTTPException | ||
| from pydantic import BaseModel, field_validator, HttpUrl | ||
| from pydantic import BaseModel, field_validator, HttpUrl, ConfigDict | ||
|
|
||
| from validation import ( | ||
| validate_name, | ||
| validate_image, | ||
| validate_gender, | ||
| validate_birth_date | ||
| ) | ||
|
|
||
| # Write your code here | ||
| class ProfileCreateSchema(BaseModel): | ||
| first_name: str | ||
| last_name: str | ||
| gender: str | ||
| date_of_birth: date | ||
| info: str | ||
|
Comment on lines
+13
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The task requirements for this file state that the schema should include validation for the |
||
|
|
||
| @field_validator("first_name", "last_name") | ||
| @classmethod | ||
| def chek_name(cls, v: str): | ||
|
||
| validate_name(v) | ||
| return v | ||
|
|
||
| @field_validator("gender") | ||
| @classmethod | ||
| def check_gender(cls, v: str): | ||
| validate_gender(v) | ||
| return v | ||
|
|
||
|
|
||
| @field_validator("date_of_birth") | ||
| @classmethod | ||
| def validate_birth_date(cls, v: date): | ||
| validate_birth_date(v) | ||
| return v | ||
|
|
||
|
|
||
| @field_validator("info") | ||
| @classmethod | ||
| def check_info(cls, v: str): | ||
| if not v or v.isspace(): | ||
| raise ValueError("Info cannot be empty or consist only of spaces.") | ||
| return v | ||
|
|
||
|
|
||
| class ProfileResponseSchema(BaseModel): | ||
| id: int | ||
| user_id: int | ||
| first_name: str | ||
| last_name: str | ||
| gender: str | ||
| date_of_birth: date | ||
| info: str | ||
| avatar: Optional[HttpUrl] = None | ||
|
|
||
| model_config = ConfigDict(from_attributes=True) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The endpoint path is defined as
/here, but the task requirements specify it should be/users/{user_id}/profile/. This change is crucial as it affects how you'll handle authorization (e.g., allowing an admin to create a profile for another user). The function signature will also need to be updated to acceptuser_idfrom the path.