-
Notifications
You must be signed in to change notification settings - Fork 265
solution v1 #273
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 v1 #273
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,100 @@ | ||
| from fastapi import APIRouter | ||
| from datetime import datetime, timezone | ||
|
|
||
| from fastapi import Depends, HTTPException, status, APIRouter | ||
| from sqlalchemy import cast, select | ||
| from sqlalchemy.exc import SQLAlchemyError | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
| from database import get_db, UserModel, UserProfileModel, UserGroupModel, UserGroupEnum | ||
| from config import get_s3_storage_client, get_jwt_auth_manager | ||
| from exceptions import BaseSecurityError | ||
| from storages.interfaces import S3StorageInterface | ||
| from security.interfaces import JWTAuthManagerInterface | ||
| from security.http import get_token | ||
|
|
||
| import schemas | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| # Write your code here | ||
|
|
||
|
|
||
| @router.post("/users/{user_id}/profile/", status_code=status.HTTP_201_CREATED) | ||
| async def user_profile_creation( | ||
| user_id: int, | ||
| profile_data: schemas.ProfileCreateRequestSchema, | ||
|
||
| token: str = Depends(get_token), | ||
| db: AsyncSession = Depends(get_db), | ||
| jwt_manager: JWTAuthManagerInterface = Depends(get_jwt_auth_manager), | ||
| s3_client: S3StorageInterface = Depends(get_s3_storage_client) | ||
| ) -> schemas.ProfileResponseSchema: | ||
| try: | ||
|
|
||
| payload = jwt_manager.decode_access_token(token) | ||
| current_user_id = payload.get("user_id") | ||
| except BaseSecurityError: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_401_UNAUTHORIZED, | ||
| detail="Token has expired." | ||
| ) | ||
|
|
||
| request = await db.execute( | ||
| select(UserModel).filter_by(id=current_user_id) | ||
| ) | ||
| db_current_user = request.scalar_one_or_none() | ||
|
|
||
| if not db_current_user: | ||
| raise HTTPException(status_code=401, detail="User not found.") | ||
|
||
|
|
||
| stmt = select( | ||
| UserGroupModel.id | ||
| ).where(UserGroupModel.name == UserGroupEnum.ADMIN) | ||
| db_group_admin_id = await db.scalar(stmt) | ||
|
|
||
| if current_user_id != user_id and db_current_user.group_id != db_group_admin_id: | ||
| raise HTTPException( | ||
| status_code=403, | ||
| detail="You don't have permission to edit this profile." | ||
| ) | ||
|
|
||
| request = await db.execute(select(UserModel).filter_by(id=user_id)) | ||
|
|
||
| db_user = request.scalar_one_or_none() | ||
| if not db_user or not db_user.is_active: | ||
| raise HTTPException( | ||
| status_code=401, | ||
| detail="User not found or not active." | ||
| ) | ||
|
|
||
| request = await db.execute( | ||
| select(UserProfileModel).where(UserProfileModel.user_id == db_user.id) | ||
| ) | ||
|
|
||
| db_user_profile = request.scalar_one_or_none() | ||
|
|
||
| if db_user_profile: | ||
| raise HTTPException(status_code=400, detail="User already has a profile.") | ||
|
|
||
| try: | ||
| avatar_url = await s3_client.upload(profile_data.avatar) | ||
| except Exception: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="Failed to upload avatar. Please try again later." | ||
| ) | ||
|
|
||
| # БЛОК 2: Пишем в БД (если Блок 1 прошел успешно) | ||
| db_user_profile = UserProfileModel( | ||
| user_id=user_id, | ||
| first_name=profile_data.first_name, | ||
| last_name=profile_data.last_name, | ||
| avatar=avatar_url, | ||
| gender=profile_data.gender, | ||
| date_of_birth=profile_data.date_of_birth, | ||
| info=profile_data.info | ||
|
|
||
| ) | ||
| db.add(db_user_profile) | ||
| await db.commit() | ||
| await db.refresh(db_user_profile) | ||
|
|
||
| return schemas.ProfileResponseSchema.model_validate(db_user_profile) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import datetime | ||
| from datetime import date | ||
| from typing import Any, Self | ||
|
|
||
| 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, | ||
|
|
@@ -10,4 +12,50 @@ | |
| validate_birth_date | ||
| ) | ||
|
|
||
|
|
||
| # Write your code here | ||
| class UserProfileBase(BaseModel): | ||
| first_name: str | ||
| last_name: str | ||
| gender: str | ||
| date_of_birth: date | ||
| info: str | ||
| avatar: str | ||
|
|
||
| @field_validator("first_name", "last_name") | ||
| @classmethod | ||
| def validate_fullname(cls, value): | ||
| return validate_name(value) | ||
|
|
||
| @field_validator("gender") | ||
| @classmethod | ||
| def validate_gender(cls, value): | ||
| return validate_gender(value) | ||
|
|
||
| @field_validator("date_of_birth") | ||
| @classmethod | ||
| def validate_bdate(cls, value): | ||
| return validate_birth_date(value) | ||
|
|
||
| @field_validator("info") | ||
| @classmethod | ||
| def validate_info(cls, value): | ||
| if not value.strip(): | ||
| raise ValueError("Info cannot be empty or consist only of spaces.") | ||
| return value | ||
|
|
||
| @field_validator("avatar") | ||
| @classmethod | ||
| def validate_avatar(cls, value): | ||
| return validate_image(value) | ||
|
|
||
|
Comment on lines
+18
to
+57
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 structure of this base class creates a conflict for the
To resolve this, consider defining |
||
|
|
||
| class ProfileCreateRequestSchema(UserProfileBase): | ||
| pass | ||
|
|
||
|
|
||
| class ProfileResponseSchema(UserProfileBase): | ||
| id: int | ||
| user_id: int | ||
|
|
||
| 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.
This hardcoded URL is inconsistent with the others in this file (e.g., in
activate_accounton line 233), as it is missing the port number:8000. This could prevent the link from working correctly in the development environment. Please ensure all generated URLs are valid and consistent.