From d52d1d6b759aff6b495fdec76af293e608ec003c Mon Sep 17 00:00:00 2001 From: Backend IM Bot Date: Tue, 15 Apr 2025 17:35:05 +0000 Subject: [PATCH] feat: Updated endpoint endpoints/contact.post.py via AI with auto lint fixes --- ...20250415_173454_a1756c12_update_contact.py | 29 ++++++++ endpoints/contact.post.py | 25 +++++-- helpers/contact_helpers.py | 71 ++++++++++++------- models/contact.py | 6 +- 4 files changed, 95 insertions(+), 36 deletions(-) create mode 100644 alembic/versions/20250415_173454_a1756c12_update_contact.py diff --git a/alembic/versions/20250415_173454_a1756c12_update_contact.py b/alembic/versions/20250415_173454_a1756c12_update_contact.py new file mode 100644 index 0000000..4a6a2be --- /dev/null +++ b/alembic/versions/20250415_173454_a1756c12_update_contact.py @@ -0,0 +1,29 @@ +"""create contacts table +Revision ID: 8a3d5f2e1c9b +Revises: 0002 +Create Date: 2024-01-23 10:00:00.000000 +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '8a3d5f2e1c9b' +down_revision = '0002' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'contacts', + sa.Column('id', sa.String(36), primary_key=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('message', sa.Text(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now()) + ) + op.create_index(op.f('ix_contacts_email'), 'contacts', ['email'], unique=False) + +def downgrade(): + op.drop_index(op.f('ix_contacts_email'), table_name='contacts') + op.drop_table('contacts') \ No newline at end of file diff --git a/endpoints/contact.post.py b/endpoints/contact.post.py index b1a88a9..b91a65f 100644 --- a/endpoints/contact.post.py +++ b/endpoints/contact.post.py @@ -1,16 +1,27 @@ -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from core.database import get_db from schemas.contact import ContactCreate, ContactSchema -from utils.contact_helpers import sanitize_contact_input, create_contact, format_contact_response +from typing import Dict, Any +from utils.contact_utils import sanitize_contact_input, validate_contact_data +from services.contact_service import create_contact, format_contact_response router = APIRouter() -@router.post("/contact", response_model=ContactSchema, status_code=status.HTTP_201_CREATED) +@router.post("/contact", status_code=status.HTTP_201_CREATED, response_model=ContactSchema) async def create_contact_submission( - contact_data: ContactCreate, + contact_data: Dict[str, Any], db: Session = Depends(get_db) ): - sanitized_data = sanitize_contact_input(contact_data.dict()) - contact = create_contact(db=db, contact_data=ContactCreate(**sanitized_data)) - return format_contact_response(contact) \ No newline at end of file + sanitized_data = sanitize_contact_input(contact_data) + validation_errors = validate_contact_data(sanitized_data) + + if validation_errors: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=validation_errors + ) + + contact_create = ContactCreate(**sanitized_data) + db_contact = create_contact(db=db, contact_data=contact_create) + return format_contact_response(db_contact) \ No newline at end of file diff --git a/helpers/contact_helpers.py b/helpers/contact_helpers.py index e9c9144..663de58 100644 --- a/helpers/contact_helpers.py +++ b/helpers/contact_helpers.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from models.contact import Contact from schemas.contact import ContactCreate, ContactSchema from fastapi import HTTPException, status -import email_validator +from pydantic import EmailStr, ValidationError def validate_contact_data(contact_data: Dict[str, Any]) -> Dict[str, str]: """ @@ -28,8 +28,8 @@ def validate_contact_data(contact_data: Dict[str, Any]) -> Dict[str, str]: errors["email"] = "Email is required" else: try: - email_validator.validate_email(contact_data["email"]) - except email_validator.EmailNotValidError: + EmailStr.validate(contact_data["email"]) + except ValidationError: errors["email"] = "Invalid email format - please provide a valid email address" # Validate message @@ -52,23 +52,23 @@ def create_contact(db: Session, contact_data: ContactCreate) -> Contact: Contact: The newly created contact object. Raises: - HTTPException: If there are validation errors. + HTTPException: If there are validation errors with specific field details. """ - # Validate data - validation_errors = validate_contact_data(contact_data.dict()) - if validation_errors: + try: + # ContactCreate schema will handle validation + validated_data = contact_data.dict() + db_contact = Contact(**validated_data) + db.add(db_contact) + db.commit() + db.refresh(db_contact) + return db_contact + except ValidationError as e: + errors = {error["loc"][0]: error["msg"] for error in e.errors()} raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=validation_errors + detail=errors ) - # Create new contact - db_contact = Contact(**contact_data.dict()) - db.add(db_contact) - db.commit() - db.refresh(db_contact) - return db_contact - def format_contact_response(contact: Contact) -> ContactSchema: """ Formats a contact database object into the response schema. @@ -83,32 +83,51 @@ def format_contact_response(contact: Contact) -> ContactSchema: def sanitize_contact_input(contact_data: Dict[str, Any]) -> Dict[str, Any]: """ - Sanitizes contact form input data with enhanced validation. + Sanitizes contact form input data with enhanced validation for required fields. Args: contact_data (Dict[str, Any]): Raw contact form data. Returns: Dict[str, Any]: Sanitized contact form data. + + Raises: + HTTPException: If required fields are missing or invalid with specific field details. """ + errors = {} sanitized = {} + # Sanitize and validate name if "name" in contact_data: sanitized["name"] = contact_data["name"].strip() + if not sanitized["name"]: + errors["name"] = "Name cannot be empty or consist of only whitespace" + else: + errors["name"] = "Name is required" + # Sanitize and validate email if "email" in contact_data: - sanitized["email"] = contact_data["email"].lower().strip() + email = contact_data["email"].lower().strip() + try: + EmailStr.validate(email) + sanitized["email"] = email + except ValidationError: + errors["email"] = "Invalid email format" + else: + errors["email"] = "Email is required" + # Sanitize and validate message if "message" in contact_data: sanitized["message"] = contact_data["message"].strip() - - # Ensure all required fields are present - required_fields = ["name", "email", "message"] - for field in required_fields: - if field not in sanitized or not sanitized[field]: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"{field.capitalize()} is required" - ) + if not sanitized["message"]: + errors["message"] = "Message cannot be empty or consist of only whitespace" + else: + errors["message"] = "Message is required" + + if errors: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=errors + ) return sanitized \ No newline at end of file diff --git a/models/contact.py b/models/contact.py index e41dd2c..4b6dceb 100644 --- a/models/contact.py +++ b/models/contact.py @@ -8,8 +8,8 @@ class Contact(Base): __tablename__ = "contacts" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - name = Column(String, nullable=False) - email = Column(String, nullable=False, index=True) - message = Column(Text, nullable=False) + name = Column(String, nullable=False, info={'min_length': 1, 'description': 'Contact name'}) + email = Column(String, nullable=False, index=True, info={'description': 'Contact email address'}) + message = Column(Text, nullable=False, info={'min_length': 1, 'description': 'Contact message'}) created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file