diff --git a/.env b/.env index a98864b..861bce6 100644 --- a/.env +++ b/.env @@ -11,12 +11,10 @@ JWT_ALGORITHM=RS256 CLIENT_ORIGIN=http://localhost:3000 -VERIFICATION_SECRET=my-email-verification-secret - EMAIL_HOST=smtp.mailtrap.io EMAIL_PORT=587 -EMAIL_USERNAME=4aeca0c9318dd2 -EMAIL_PASSWORD=a987a0e0eac00d +EMAIL_USERNAME=41fa1d7c714ab0 +EMAIL_PASSWORD=608f76636315f5 EMAIL_FROM=admin@admin.com JWT_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVmlQWlJyVFpjd3l4RVhVRGpNaFZuCi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUUpBYm5LaENOQ0dOSFZGaHJPQ0RCU0IKdmZ2ckRWUzVpZXAwd2h2SGlBUEdjeWV6bjd0U2RweUZ0NEU0QTNXT3VQOXhqenNjTFZyb1pzRmVMUWlqT1JhUwp3UUloQU84MWl2b21iVGhjRkltTFZPbU16Vk52TGxWTW02WE5iS3B4bGh4TlpUTmhBaUVBbWRISlpGM3haWFE0Cm15QnNCeEhLQ3JqOTF6bVFxU0E4bHUvT1ZNTDNSak1DSVFEbDJxOUdtN0lMbS85b0EyaCtXdnZabGxZUlJPR3oKT21lV2lEclR5MUxaUVFJZ2ZGYUlaUWxMU0tkWjJvdXF4MHdwOWVEejBEWklLVzVWaSt6czdMZHRDdUVDSUVGYwo3d21VZ3pPblpzbnU1clBsTDJjZldLTGhFbWwrUVFzOCtkMFBGdXlnCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t diff --git a/alembic/versions/1c7984990e1d_created_posts_table.py b/alembic/versions/1c7984990e1d_created_posts_table.py deleted file mode 100644 index 76882a0..0000000 --- a/alembic/versions/1c7984990e1d_created_posts_table.py +++ /dev/null @@ -1,39 +0,0 @@ -"""created_posts_table - -Revision ID: 1c7984990e1d -Revises: 15770e820938 -Create Date: 2022-07-06 23:15:42.761079 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = '1c7984990e1d' -down_revision = '15770e820938' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('posts', - sa.Column('id', postgresql.UUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False), - sa.Column('user_id', postgresql.UUID(), nullable=False), - sa.Column('title', sa.String(), nullable=False), - sa.Column('content', sa.String(), nullable=False), - sa.Column('category', sa.String(), nullable=False), - sa.Column('image', sa.String(), nullable=False), - sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False), - sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('posts') - # ### end Alembic commands ### diff --git a/alembic/versions/39256113e8e5_added_verification_code.py b/alembic/versions/39256113e8e5_added_verification_code.py deleted file mode 100644 index 9eca0eb..0000000 --- a/alembic/versions/39256113e8e5_added_verification_code.py +++ /dev/null @@ -1,43 +0,0 @@ -"""added verification code - -Revision ID: 39256113e8e5 -Revises: 1c7984990e1d -Create Date: 2022-07-14 08:03:57.507140 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = '39256113e8e5' -down_revision = '1c7984990e1d' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('posts') - op.add_column('users', sa.Column('verification_code', sa.String(), nullable=True)) - op.create_unique_constraint(None, 'users', ['verification_code']) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'users', type_='unique') - op.drop_column('users', 'verification_code') - op.create_table('posts', - sa.Column('id', postgresql.UUID(), server_default=sa.text('uuid_generate_v4()'), autoincrement=False, nullable=False), - sa.Column('user_id', postgresql.UUID(), autoincrement=False, nullable=False), - sa.Column('title', sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column('content', sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column('category', sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column('image', sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False), - sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='posts_user_id_fkey', ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id', name='posts_pkey') - ) - # ### end Alembic commands ### diff --git a/alembic/versions/15770e820938_created_users_table.py b/alembic/versions/b1ec015461c4_added_verification_auth.py similarity index 77% rename from alembic/versions/15770e820938_created_users_table.py rename to alembic/versions/b1ec015461c4_added_verification_auth.py index 45ccf97..af93bf9 100644 --- a/alembic/versions/15770e820938_created_users_table.py +++ b/alembic/versions/b1ec015461c4_added_verification_auth.py @@ -1,8 +1,8 @@ -"""created_users_table +"""added-verification-auth -Revision ID: 15770e820938 +Revision ID: b1ec015461c4 Revises: -Create Date: 2022-07-06 15:11:26.439123 +Create Date: 2022-08-26 20:35:54.899416 """ from alembic import op @@ -10,7 +10,7 @@ from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. -revision = '15770e820938' +revision = 'b1ec015461c4' down_revision = None branch_labels = None depends_on = None @@ -19,17 +19,19 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table('users', - sa.Column('id', postgresql.UUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False), + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), sa.Column('name', sa.String(), nullable=False), sa.Column('email', sa.String(), nullable=False), sa.Column('password', sa.String(), nullable=False), sa.Column('photo', sa.String(), nullable=True), sa.Column('verified', sa.Boolean(), server_default='False', nullable=False), + sa.Column('verification_code', sa.String(), nullable=True), sa.Column('role', sa.String(), server_default='user', nullable=False), sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False), sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') + sa.UniqueConstraint('email'), + sa.UniqueConstraint('verification_code') ) # ### end Alembic commands ### diff --git a/app/config.py b/app/config.py index cd46047..86bdbe9 100644 --- a/app/config.py +++ b/app/config.py @@ -17,8 +17,6 @@ class Settings(BaseSettings): CLIENT_ORIGIN: str - VERIFICATION_SECRET: str - EMAIL_HOST: str EMAIL_PORT: int EMAIL_USERNAME: str diff --git a/app/email.py b/app/email.py index adbeb6c..0bdcf49 100644 --- a/app/email.py +++ b/app/email.py @@ -32,8 +32,8 @@ async def sendMail(self, subject, template): MAIL_FROM=settings.EMAIL_FROM, MAIL_PORT=settings.EMAIL_PORT, MAIL_SERVER=settings.EMAIL_HOST, - MAIL_TLS=True, - MAIL_SSL=False, + MAIL_STARTTLS=False, + MAIL_SSL_TLS=False, USE_CREDENTIALS=True, VALIDATE_CERTS=True ) diff --git a/app/models.py b/app/models.py index 2f6b9fd..639dea1 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,3 @@ -from enum import unique import uuid from .database import Base from sqlalchemy import TIMESTAMP, Column, String, Boolean, text diff --git a/app/routers/auth.py b/app/routers/auth.py index cf0e725..5d3612a 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -150,6 +150,9 @@ def verify_me(token: str, db: Session = Depends(get_db)): db.commit() user = user_query.first() if not user: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Invalid code or user doesn't exist") + if user.verified: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail='Email can only be verified once') user_query.update( diff --git a/readMe.md b/readMe.md index b480aca..18208f0 100644 --- a/readMe.md +++ b/readMe.md @@ -1,5 +1,31 @@ -# RESTful API with Python, FastAPI, Pydantic, SQLAlchemy and Docker +# RESTful API with Python, SQLAlchemy, & FastAPI: Send HTML Emails -### 1. RESTful API with Python & FastAPI: Access and Refresh Tokens +In this article, you'll learn how to send HTML emails with Python, FastAPI, SQLAlchemy, PostgreSQL, Jinja2, and Docker-compose. Also, you'll learn how to dynamically generate HTML templates with the Jinja2 package. -[RESTful API with Python & FastAPI: Access and Refresh Tokens](https://codevoweb.com/restful-api-with-python-fastapi-access-and-refresh-tokens) +![RESTful API with Python, SQLAlchemy, & FastAPI: Send HTML Emails](https://codevoweb.com/wp-content/uploads/2022/07/RESTful-API-with-Python-FastAPI-Send-HTML-Emails.webp) + +## Topics Covered + +- Send HTML Email with jinja2 and FastAPI Overview +- Creating an SMTP Provider Account +- Validating the Environment Variables with Pydantic +- Create a Database Model with Sqlalchemy +- Creating the HTML Email Templates with Jinja2 +- Set up SMTP Email Sender +- How to Send the HTML Email +- Update the SignUp Path Operation Function +- Create a Controller to Verify the Code + +Read the entire article here: [https://codevoweb.com/restful-api-with-python-fastapi-send-html-emails](https://codevoweb.com/restful-api-with-python-fastapi-send-html-emails) + +### 1. RESTful API with Python,SQLAlchemy, & FastAPI: Access and Refresh Tokens + +[RESTful API with Python, SQLAlchemy, & FastAPI: Access and Refresh Tokens](https://codevoweb.com/restful-api-with-python-fastapi-access-and-refresh-tokens) + +### 2. RESTful API with Python, SQLAlchemy, & FastAPI: Send HTML Emails + +[RESTful API with Python, SQLAlchemy & FastAPI: Send HTML Emails](https://codevoweb.com/restful-api-with-python-fastapi-send-html-emails) + +### 3. CRUD RESTful API Server with Python, FastAPI, SQLAlchemy, and PostgreSQL + +[CRUD RESTful API Server with Python, FastAPI, SQLAlchemy, and PostgreSQL](https://codevoweb.com/crud-restful-api-server-with-python-fastapi-and-postgresql) diff --git a/requirements.txt b/requirements.txt index 2c14003..095a868 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,46 +1,44 @@ -alembic==1.8.0 -anyio==3.6.1 -asgiref==3.5.2 -autopep8==1.6.0 -bcrypt==3.2.2 -certifi==2022.6.15 +aiosmtplib==1.1.7 +alembic==1.9.0 +anyio==3.6.2 +bcrypt==4.0.1 +blinker==1.5 +certifi==2022.12.7 cffi==1.15.1 -charset-normalizer==2.1.0 click==8.1.3 -colorama==0.4.5 +colorama==0.4.6 cryptography==3.4.8 dnspython==2.2.1 -email-validator==1.2.1 -fastapi==0.78.0 +email-validator==1.3.0 +fastapi==0.87.0 fastapi-jwt-auth==0.5.0 -greenlet==1.1.2 -h11==0.13.0 -httptools==0.4.0 -idna==3.3 +fastapi-mail==1.2.2 +greenlet==2.0.1 +h11==0.14.0 +httpcore==0.16.3 +httptools==0.5.0 +httpx==0.23.1 +idna==3.4 itsdangerous==2.1.2 Jinja2==3.1.2 -Mako==1.2.1 +Mako==1.2.4 MarkupSafe==2.1.1 -orjson==3.7.6 +orjson==3.8.3 passlib==1.7.4 -psycopg2==2.9.3 -pycodestyle==2.8.0 +psycopg2==2.9.5 pycparser==2.21 -pydantic==1.9.1 +pydantic==1.10.2 PyJWT==1.7.1 -python-dotenv==0.20.0 +python-dotenv==0.21.0 python-multipart==0.0.5 PyYAML==6.0 -requests==2.28.1 +rfc3986==1.5.0 six==1.16.0 -sniffio==1.2.0 -SQLAlchemy==1.4.39 -starlette==0.19.1 -toml==0.10.2 -typing_extensions==4.3.0 -tzdata==2022.1 -ujson==5.4.0 -urllib3==1.26.9 -uvicorn==0.17.6 -watchgod==0.8.2 -websockets==10.3 +sniffio==1.3.0 +SQLAlchemy==1.4.45 +starlette==0.21.0 +typing_extensions==4.4.0 +ujson==5.6.0 +uvicorn==0.20.0 +watchfiles==0.18.1 +websockets==10.4 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy