From 69e484cafebae185601b3233da9c0acaa95f4911 Mon Sep 17 00:00:00 2001 From: Backend IM Bot Date: Wed, 23 Apr 2025 23:18:12 +0000 Subject: [PATCH] feat: Generated endpoint endpoints/email.post.py via AI for Contact and updated requirements.txt --- ...20250423_231745_2b25941c_update_contact.py | 31 +++++++ endpoints/email.post.py | 28 ++++++ helpers/contact_helpers.py | 93 +++++++++++++++++++ models/contact.py | 16 ++++ requirements.txt | 4 + schemas/contact.py | 35 +++++++ 6 files changed, 207 insertions(+) create mode 100644 alembic/versions/20250423_231745_2b25941c_update_contact.py create mode 100644 helpers/contact_helpers.py create mode 100644 models/contact.py create mode 100644 schemas/contact.py diff --git a/alembic/versions/20250423_231745_2b25941c_update_contact.py b/alembic/versions/20250423_231745_2b25941c_update_contact.py new file mode 100644 index 0000000..1759f6d --- /dev/null +++ b/alembic/versions/20250423_231745_2b25941c_update_contact.py @@ -0,0 +1,31 @@ +"""create table for Contact +Revision ID: a1b2c3d4e5f6 +Revises: 0001 +Create Date: 2023-11-22 12:34:56.789012 +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '2b25941c' +down_revision = '0001' +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/email.post.py b/endpoints/email.post.py index e69de29..ed90947 100644 --- a/endpoints/email.post.py +++ b/endpoints/email.post.py @@ -0,0 +1,28 @@ +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 helpers.contact_helpers import handle_contact_submission + +router = APIRouter() + +@router.post("/email", status_code=status.HTTP_201_CREATED, response_model=ContactSchema) +async def submit_contact_form( + contact_data: ContactCreate, + db: Session = Depends(get_db) +): + """ + Submit a contact form with name, email, and message. + All fields are required and email must be in valid format. + """ + try: + contact = handle_contact_submission(db=db, contact_data=contact_data.dict()) + return contact + except HTTPException: + # Re-raise the HTTP exception from the helper + raise + except Exception as e: + raise HTTPException( + status_code=500, + detail={"message": "An error occurred processing your request", "error": str(e)} + ) \ No newline at end of file diff --git a/helpers/contact_helpers.py b/helpers/contact_helpers.py new file mode 100644 index 0000000..230e129 --- /dev/null +++ b/helpers/contact_helpers.py @@ -0,0 +1,93 @@ +from typing import Dict, Any +from sqlalchemy.orm import Session +from fastapi import HTTPException +from email_validator import validate_email, EmailNotValidError +from models.contact import Contact +from schemas.contact import ContactCreate + +def validate_contact_data(contact_data: Dict[str, Any]) -> Dict[str, str]: + """ + Validates contact form data, checking for required fields and email format. + + Args: + contact_data (Dict[str, Any]): The contact form data to validate + + Returns: + Dict[str, str]: Dictionary of validation errors, empty if validation passes + """ + errors = {} + + # Check required fields + if not contact_data.get("name"): + errors["name"] = "Name is required" + + if not contact_data.get("email"): + errors["email"] = "Email is required" + else: + # Validate email format + try: + validate_email(contact_data["email"]) + except EmailNotValidError: + errors["email"] = "Invalid email format" + + if not contact_data.get("message"): + errors["message"] = "Message is required" + + return errors + +def create_contact(db: Session, contact_data: ContactCreate) -> Contact: + """ + Creates a new contact submission in the database. + + Args: + db (Session): The database session + contact_data (ContactCreate): The validated contact data + + Returns: + Contact: The newly created contact object + """ + db_contact = Contact( + name=contact_data.name, + email=contact_data.email, + message=contact_data.message + ) + + db.add(db_contact) + db.commit() + db.refresh(db_contact) + + return db_contact + +def handle_contact_submission(db: Session, contact_data: Dict[str, Any]) -> Contact: + """ + Handles the complete contact form submission process including validation + and database creation. + + Args: + db (Session): The database session + contact_data (Dict[str, Any]): The raw contact form data + + Returns: + Contact: The created contact object + + Raises: + HTTPException: If validation fails with a 400 status code and detailed error messages + """ + # Validate the contact data + validation_errors = validate_contact_data(contact_data) + + if validation_errors: + raise HTTPException( + status_code=400, + detail={"message": "Validation error", "errors": validation_errors} + ) + + # Create a ContactCreate instance from the validated data + contact_create = ContactCreate( + name=contact_data["name"], + email=contact_data["email"], + message=contact_data["message"] + ) + + # Create the contact in the database + return create_contact(db, contact_create) \ No newline at end of file diff --git a/models/contact.py b/models/contact.py new file mode 100644 index 0000000..914a82d --- /dev/null +++ b/models/contact.py @@ -0,0 +1,16 @@ +from sqlalchemy import Column, String, DateTime, Text +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.sql import func +from core.database import Base +import uuid + +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) + + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 596e6f3..ec13e88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,7 @@ sqlalchemy>=1.4.0 python-dotenv>=0.19.0 bcrypt>=3.2.0 alembic>=1.13.1 +email_validator +jose +passlib +pydantic diff --git a/schemas/contact.py b/schemas/contact.py new file mode 100644 index 0000000..3241def --- /dev/null +++ b/schemas/contact.py @@ -0,0 +1,35 @@ +from pydantic import BaseModel, Field, EmailStr +from typing import Optional +from datetime import datetime +from uuid import UUID + +class ContactBase(BaseModel): + name: str = Field(..., description="Contact's name") + email: EmailStr = Field(..., description="Contact's email address") + message: str = Field(..., description="Contact's message") + +class ContactCreate(ContactBase): + pass + +class ContactUpdate(BaseModel): + name: Optional[str] = Field(None, description="Contact's name") + email: Optional[EmailStr] = Field(None, description="Contact's email address") + message: Optional[str] = Field(None, description="Contact's message") + +class ContactSchema(ContactBase): + id: UUID + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True + schema_extra = { + "example": { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "name": "John Doe", + "email": "john.doe@example.com", + "message": "This is a test message", + "created_at": "2023-01-01T12:00:00", + "updated_at": "2023-01-01T12:00:00" + } + } \ No newline at end of file