Skip to content
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

Feature to deactivate a user #150

Merged
merged 28 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6a7a0f7
Implement user deactivation
joboy-dev Jul 21, 2024
bc8646c
Merge branch 'dev' of https://github.com/joboy-dev/hng_boilerplate_py…
joboy-dev Jul 21, 2024
0536f3c
feat: user deactivation
joboy-dev Jul 21, 2024
eb81cfe
Merge branch 'dev' of https://github.com/joboy-dev/hng_boilerplate_py…
joboy-dev Jul 21, 2024
dfe0ae5
Made requested changes
joboy-dev Jul 21, 2024
645d20b
Made requested changes
joboy-dev Jul 21, 2024
b286158
Added changes to email
joboy-dev Jul 21, 2024
564c165
Feat: Add reactivation endpoint
joboy-dev Jul 21, 2024
954e8cf
Feat: Add reactivation endpoint
joboy-dev Jul 21, 2024
8e97391
Feat: Add reactivation endpoint
joboy-dev Jul 21, 2024
c05b866
Tests updated
joboy-dev Jul 21, 2024
100afe3
Tests updated
joboy-dev Jul 21, 2024
b84aa80
Tests updated
joboy-dev Jul 21, 2024
04f0c82
Tests updated
joboy-dev Jul 21, 2024
7085ff2
Tests updated
joboy-dev Jul 21, 2024
c40f88a
Tests updated
joboy-dev Jul 21, 2024
e749528
Reconfigure tests
joboy-dev Jul 21, 2024
b9b4d57
Update user deactivation tests
joboy-dev Jul 21, 2024
d5c3842
Update user deactivation tests
joboy-dev Jul 21, 2024
e9ab50c
Update user deactivation tests
joboy-dev Jul 21, 2024
8a3723f
Update user deactivation tests
joboy-dev Jul 21, 2024
8da88ae
Update user deactivation tests
joboy-dev Jul 21, 2024
f0bd84b
Update user deactivation tests
joboy-dev Jul 21, 2024
ef09c50
Update user deactivation tests
joboy-dev Jul 21, 2024
13e406e
Update tests to mock db
joboy-dev Jul 21, 2024
c96c68e
Update tests to mock db
joboy-dev Jul 21, 2024
eaac58a
Update to tests
joboy-dev Jul 21, 2024
0a55da5
Update to tests
joboy-dev Jul 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ ALGORITHM = HS256
ACCESS_TOKEN_EXPIRE_MINUTES = 10
JWT_REFRESH_EXPIRY=5
APP_URL=

MAIL_USERNAME=""
MAIL_PASSWORD=""
MAIL_FROM=""
MAIL_PORT=465
MAIL_SERVER="smtp.gmail.com"
28 changes: 28 additions & 0 deletions api/core/dependencies/email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import smtplib
from typing import List, Optional, Union

from fastapi import HTTPException
from api.utils.settings import settings


class MailService:
'''Class to send different emails for different services'''

def send_mail(self, to: str, subject: str, body: str):
'''Function to send email to a user either as a regular test or as html file'''

try:
with smtplib.SMTP(settings.MAIL_SERVER, settings.MAIL_PORT) as conn:
conn.starttls()
conn.login(user=settings.MAIL_USERNAME, password=settings.MAIL_PASSWORD)
conn.sendmail(
from_addr=settings.MAIL_FROM,
to_addrs=to,
msg=f"Subject:{subject}\n\n{body}"
)

except smtplib.SMTPException as smtp_error:
raise HTTPException(500, f'SMTP ERROR- {smtp_error}')


mail_service = MailService()
6 changes: 6 additions & 0 deletions api/utils/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,11 @@ class Settings(BaseSettings):
DB_NAME: str = config("DB_NAME")
DB_TYPE: str = config("DB_TYPE")

MAIL_USERNAME: str = config("MAIL_USERNAME")
MAIL_PASSWORD: str = config('MAIL_PASSWORD')
MAIL_FROM: str = config('MAIL_FROM')
MAIL_PORT: int = config('MAIL_PORT')
MAIL_SERVER: str = config('MAIL_SERVER')


settings = Settings()
81 changes: 81 additions & 0 deletions api/v1/routes/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from fastapi import Depends, HTTPException, APIRouter, Request
from jose import JWTError
import jwt
from sqlalchemy.orm import Session

from api.core.dependencies.email import mail_service
from api.utils.auth import create_access_token

from api.utils.config import SECRET_KEY, ALGORITHM
from ..models.user import User
from api.v1.schemas.user import DeactivateUserSchema
from api.db.database import get_db
from api.utils.dependencies import get_current_user


user = APIRouter(prefix='/api/v1/users', tags=['Users'])

@user.patch('/accounts/deactivate', status_code=200)
async def deactivate_account(request: Request, schema: DeactivateUserSchema, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
'''Endpoint to deactivate a user account'''

# Generate an access token containing user credentials
token = create_access_token(data={'username': f'{user.username}'})

if not user.is_active:
raise HTTPException(400, 'User is inactive')

if schema.confirmation == False:
raise HTTPException(400, 'Confirmation required to deactivate account')

Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A proper confirmation should be done by sending the user an email with a deactivation link

Boss @zxenonx what do you think?

Copy link
Contributor Author

@joboy-dev joboy-dev Jul 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that before but the person working on adding the emailing template to the database doesn't have it ready so I was told to implement what I had, that a central service would be used for emailing
The endpoint to even send the email was available but the person too could not proceed without the one adding the templates to the database.

Also, the confirmation email would be sent after the account has been deactivated successfully.
@Dev-wonderful

Copy link
Contributor

@Dev-wonderful Dev-wonderful Jul 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was the one who said you should implement what you have. Anyway, I hope the confirmation would allow the user revert the deactivation with a time limit, since you have decided to deactivate successfully before sending a confirmation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay
Let me see what I can do about that as quickly as possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dev-wonderful I have implemented the changes

user.is_active = False

# Send aail to user
mail_service.send_mail(
to=user.email,
subject='Account deactivation',
body=f'Hello, {user.first_name},\n\nYour account has been deactivated successfully.\nTo reactivate your account if this was a mistake, please click the link below:\n{request.url.hostname}/api/users/accounts/reactivate?token={token}\n\nThis link expires after 15 minutes.'
)

# Commit changes to deactivate the user
db.commit()

return {"status_code": 200, "message": "Account deactivated successfully. Check email for confirmation"}


@user.get('/accounts/reactivate', status_code=200)
async def reactivate_account(request: Request, db: Session = Depends(get_db)):
'''Endpoint to reactivate a user account'''

# Get access token from query
token = request.query_params.get('token')

# Validate the token
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get('username')

if username is None:
raise HTTPException(400, 'Invalid token')

except JWTError:
raise HTTPException(400, 'Invalid token')

user = db.query(User).filter(User.username == username).first()

if user.is_active:
raise HTTPException(400, 'User is already active')

user.is_active = True

# Send aail to user
mail_service.send_mail(
to=user.email,
subject='Account reactivation',
body=f'Hello, {user.first_name},\n\nYour account has been reactivated successfully'
)

# Commit changes to deactivate the user
db.commit()

return {"status_code": 200, "message": "Account reactivated successfully. Check email for confirmation"}
10 changes: 10 additions & 0 deletions api/v1/schemas/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Optional
from pydantic import BaseModel


class DeactivateUserSchema(BaseModel):
'''Schema for deactivating a user'''

reason: Optional[str] = None
confirmation: bool

11 changes: 8 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
from starlette.requests import Request
from api.db.database import Base, engine

from api.v1.routes.newsletter_router import newsletter
from api.v1.routes.newsletter_router import (
CustomException,
custom_exception_handler
)

from api.v1.routes import api_version_one
from api.v1.routes.auth import auth
from api.v1.routes.user import user
from api.v1.routes.roles import role

Base.metadata.create_all(bind=engine)

Expand All @@ -35,9 +38,11 @@ async def lifespan(app: FastAPI):
)

app.add_exception_handler(CustomException, custom_exception_handler) # Newsletter custom exception registration
app.include_router(newsletter, tags=["Newsletter"])

app.include_router(api_version_one)

app.include_router(auth)
app.include_router(user)
# app.include_router(users, tags=["Users"])


@app.get("/", tags=["Home"])
Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion tests/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ def override_get_db():
finally:
session.close()
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
yield TestClient(app)

101 changes: 0 additions & 101 deletions tests/v1/test_login.py

This file was deleted.

58 changes: 0 additions & 58 deletions tests/v1/test_newsletter.py

This file was deleted.

Loading
Loading