Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…python_fastapi_web into feat/implement-status-page
  • Loading branch information
JoshuaOloton committed Aug 24, 2024
2 parents 75f4ea0 + 2544647 commit 8f2bcf5
Show file tree
Hide file tree
Showing 33 changed files with 8,600 additions and 349 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ share/python-wheels/
.installed.cfg
*.egg
MANIFEST
test_case1.py
api/core/dependencies/mailjet.py
tests/v1/waitlist/waitlist_test.py

# PyInstaller
# Usually these files are written by a python script from a template
Expand All @@ -36,6 +38,7 @@ api/core/dependencies/mailjet.py

# Installer logs
pip-log.txt
test_case1.py
pip-delete-this-directory.txt

# Unit test / coverage reports
Expand Down
42 changes: 42 additions & 0 deletions api/core/dependencies/email/templates/profile_recovery_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{% extends 'base.html' %}

{% block title %}Recovery Email Verification{% endblock %}
{% block style %}<link rel="stylesheet" href="{{ url_for('email_static', path='css/email-verification.css') }}">{% endblock %}

{% block content %}
<div class="template-main">
<div class="heading">
<p class="template-header">Recovery Email Verification</p>
</div>
<div class="content">
<p class="template-receiver-name">Hi {{ first_name }} {{ last_name }},</p>
<p class="template-message">
You have requested to change the recovery email on your profile.
</p>
<div class="editable-content">
<p>
This link will expire 5 minutes from when this email was been sent. If
you did not make this request, you can ignore this email.
</p>
<p>To change your recovery email, please click the button below:</p>
<a href="{{ link }}"><button
style="
display: inline-block;
padding: 10px 200px;
background-color: orangered;
color: #fff;
text-decoration: none;
border-radius: 10px;
">Change Recovery Email</button></a>
<p>
Or copy this link into your browser:
{{ link }}
</p>
<div class="template-farewell">
<p>Regards,</p>
<p>Boilerplate</p>
</div>
</div>
</div>
</div>
{% endblock %}
45 changes: 45 additions & 0 deletions api/core/dependencies/email/templates/unsubscribe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{% extends 'base.html' %} {% block title %}Welcome{% endblock %} {% block
content %}
<table role="presentation" width="100%" style="padding: 3.5rem">
<tr>
<td>
<div style="text-align: center; margin-bottom: 1.5rem">
<h1 style="font-size: 1.5rem; color: #0a0a0a; font-weight: 600">
Hello From The Boilerplate
</h1>
<p
style="
font-size: 1.125rem;
color: rgba(0, 0, 0, 0.8);
font-weight: 500;
"
>
Unsubscription Successful
</p>
</div>

<div>
<p style="color: #111; font-size: 1.125rem; font-weight: 600">
Hi Hope this find you well.
</p>
<p style="color: rgba(17, 17, 17, 0.9); font-weight: 400">
You have successfully Unsubscribed from our email newsletter.
</p>
<p style="color: rgba(17, 17, 17, 0.9); font-weight: 400">
This emai is a confirmation of that action. As you will not recieve
any newsletter updates from us anymore.
</p>
</div>

<!-- <div style="margin-top: 2rem;">
<p style="color: #111; font-size: 0.875rem; font-weight: 500;">Thank you for joining Boilerplate</p>
</div> -->

<div style="margin-top: 2rem">
<p>Regards,</p>
<p>Boilerplate</p>
</div>
</td>
</tr>
</table>
{% endblock %}
56 changes: 56 additions & 0 deletions api/core/dependencies/email/templates/waitlists.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{% extends 'base.html' %}

{% block title %}Welcome{% endblock %}

{% block content %}
<table role="presentation" width="100%" style="padding: 3.5rem;">
<tr>
<td>
<div style="text-align: center; margin-bottom: 1.5rem;">
<h1 style="font-size: 1.5rem; color: #0A0A0A; font-weight: 600;">Welcome to Boilerplate Waitlist</h1>
<p style="font-size: 1.125rem; color: rgba(0, 0, 0, 0.8); font-weight: 500;">Thanks for signing up</p>
</div>

<div>
<p style="color: #111; font-size: 1.125rem; font-weight: 600;">Hi {{name}}</p>
<p style="color: rgba(17, 17, 17, 0.9); font-weight: 400;">We're thrilled to have you join our waitlist. Experience quality and innovation
like never before. Our product is made to fit your needs and make your
life easier.</p>
</div>

<div style="margin-bottom: 1.75rem;">
<h3 style="color: #0A0A0A; font-weight: 600;">Here's what you can look forward to.</h3>
<div style="margin-bottom: 1.25rem;">
<ul>
<li>
<span style="font-weight: 600;">Exclusive Offers:</span> Enjoy special promotions and
discounts available only to our members.
</li>
<li>
<span style="font-weight: 600;">Exclusive Offers:</span> Enjoy special promotions and
discounts available only to our members.
</li>
<li>
<span style="font-weight: 600;">Exclusive Offers:</span> Enjoy special promotions and
discounts available only to our members.
</li>
</ul>
</div>
</div>

<a href="{{cta_link}}" style="display: block; width: fit-content; padding: 0.5rem 2.5rem; background-color: #F97316; color: white; text-decoration: none; border-radius: 0.5rem; margin: 0 auto; text-align: center;">
Learn more about us
</a>

<!-- <div style="margin-top: 2rem;">
<p style="color: #111; font-size: 0.875rem; font-weight: 500;">Thank you for joining Boilerplate</p>
</div> -->

<div style="margin-top: 2rem;">
<p>Regards,</p>
<p>Boilerplate</p>
</div>
</td>
</tr>
</table>
{% endblock %}
4 changes: 2 additions & 2 deletions api/core/dependencies/email_sender.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Optional
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig, MessageType

from api.utils.settings import settings
from premailer import transform



async def send_email(
Expand All @@ -11,7 +12,6 @@ async def send_email(
context: Optional[dict] = None
):
from main import email_templates
from premailer import transform

conf = ConnectionConfig(
MAIL_USERNAME=settings.MAIL_USERNAME,
Expand Down
6 changes: 4 additions & 2 deletions api/v1/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ class Profile(BaseTableModel):
phone_number = Column(String, nullable=True)
avatar_url = Column(String, nullable=True)
recovery_email = Column(String, nullable=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
facebook_link = Column(String, nullable=True)
instagram_link = Column(String, nullable=True)
twitter_link = Column(String, nullable=True)
linkedin_link = Column(String, nullable=True)

user = relationship("User", back_populates="profile")

Expand Down
35 changes: 28 additions & 7 deletions api/v1/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from datetime import timedelta
from slowapi import Limiter
from slowapi.util import get_remote_address

from fastapi import (BackgroundTasks, Depends,
status, APIRouter,
Response, Request)
Expand Down Expand Up @@ -27,9 +30,12 @@

auth = APIRouter(prefix="/auth", tags=["Authentication"])

# Initialize rate limiter
limiter = Limiter(key_func=get_remote_address)

@auth.post("/register", status_code=status.HTTP_201_CREATED, response_model=auth_response)
def register(background_tasks: BackgroundTasks, response: Response, user_schema: UserCreate, db: Session = Depends(get_db)):
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
def register(request: Request, background_tasks: BackgroundTasks, response: Response, user_schema: UserCreate, db: Session = Depends(get_db)):
'''Endpoint for a user to register their account'''

# Create user account
Expand Down Expand Up @@ -88,7 +94,8 @@ def register(background_tasks: BackgroundTasks, response: Response, user_schema:


@auth.post(path="/register-super-admin", status_code=status.HTTP_201_CREATED, response_model=auth_response)
def register_as_super_admin(user: UserCreate, db: Session = Depends(get_db)):
@limiter.limit("1000/minute") # Limit to 5 requests per minute per IP
def register_as_super_admin(request: Request, user: UserCreate, db: Session = Depends(get_db)):
"""Endpoint for super admin creation"""

user = user_service.create_admin(db=db, schema=user)
Expand Down Expand Up @@ -131,7 +138,8 @@ def register_as_super_admin(user: UserCreate, db: Session = Depends(get_db)):


@auth.post("/login", status_code=status.HTTP_200_OK, response_model=auth_response)
def login(login_request: LoginRequest, db: Session = Depends(get_db)):
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
def login(request: Request, login_request: LoginRequest, db: Session = Depends(get_db)):
"""Endpoint to log in a user"""

# Authenticate the user
Expand Down Expand Up @@ -171,7 +179,9 @@ def login(login_request: LoginRequest, db: Session = Depends(get_db)):


@auth.post("/logout", status_code=status.HTTP_200_OK)
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
def logout(
request: Request,
response: Response,
db: Session = Depends(get_db),
current_user: User = Depends(user_service.get_current_user),
Expand All @@ -187,6 +197,7 @@ def logout(


@auth.post("/refresh-access-token", status_code=status.HTTP_200_OK)
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
def refresh_access_token(
request: Request, response: Response, db: Session = Depends(get_db)
):
Expand Down Expand Up @@ -220,7 +231,8 @@ def refresh_access_token(


@auth.post("/request-token", status_code=status.HTTP_200_OK)
async def request_signin_token(background_tasks: BackgroundTasks,
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
async def request_signin_token(request: Request, background_tasks: BackgroundTasks,
email_schema: EmailRequest, db: Session = Depends(get_db)
):
"""Generate and send a 6-digit sign-in token to the user's email"""
Expand Down Expand Up @@ -253,7 +265,9 @@ async def request_signin_token(background_tasks: BackgroundTasks,


@auth.post("/verify-token", status_code=status.HTTP_200_OK, response_model=auth_response)
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
async def verify_signin_token(
request: Request,
token_schema: TokenRequest, db: Session = Depends(get_db)
):
"""Verify the 6-digit sign-in token and log in the user"""
Expand Down Expand Up @@ -294,11 +308,13 @@ async def verify_signin_token(

# TODO: Fix magic link authentication
@auth.post("/magic-link", status_code=status.HTTP_200_OK)
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
def request_magic_link(
request: MagicLinkRequest, background_tasks: BackgroundTasks,
request: Request,
requests: MagicLinkRequest, background_tasks: BackgroundTasks,
response: Response, db: Session = Depends(get_db)
):
user = user_service.fetch_by_email(db=db, email=request.email)
user = user_service.fetch_by_email(db=db, email=requests.email)
magic_link_token = user_service.create_access_token(user_id=user.id)
magic_link = f"https://anchor-python.teams.hng.tech/login/magic-link?token={magic_link_token}"

Expand All @@ -319,7 +335,8 @@ def request_magic_link(


@auth.post("/magic-link/verify")
async def verify_magic_link(token_schema: Token, db: Session = Depends(get_db)):
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
async def verify_magic_link(request: Request, token_schema: Token, db: Session = Depends(get_db)):
user, access_token = AuthService.verify_magic_token(token_schema.token, db)
user_organizations = organisation_service.retrieve_user_organizations(user, db)

Expand Down Expand Up @@ -352,7 +369,9 @@ async def verify_magic_link(token_schema: Token, db: Session = Depends(get_db)):


@auth.put("/password", status_code=200)
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
async def change_password(
request: Request,
schema: ChangePasswordSchema,
db: Session = Depends(get_db),
user: User = Depends(user_service.get_current_user),
Expand All @@ -369,7 +388,9 @@ async def change_password(
@auth.get("/@me",
status_code=status.HTTP_200_OK,
response_model=AuthMeResponse)
@limiter.limit("1000/minute") # Limit to 1000 requests per minute per IP
def get_current_user_details(
request: Request,
db: Annotated[Session, Depends(get_db)],
current_user: Annotated[User, Depends(user_service.get_current_user)],
):
Expand Down
Loading

0 comments on commit 8f2bcf5

Please sign in to comment.