Skip to content

Commit

Permalink
Merge pull request #103 from Qwizi/email
Browse files Browse the repository at this point in the history
Updated email service and added better error exception logging
  • Loading branch information
Qwizi authored Jan 29, 2024
2 parents ab38e66 + 163319b commit cc2a340
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 204 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ MAIL_SERVER=smtp.example.com
MAIL_FROM_NAME=randomMailFromName123
STRIPE_API_KEY=asdasdasd
STRIPE_WEBHOOK_SECRET=asdasd
SITE_URL=http://localhost:80
SITE_URL=http://localhost:80
RESEND_API_KEY=test_key
18 changes: 16 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[tool.poetry]
name = "sharkservers-api"
version = "1.2.1"
version = "1.2.2"
description = "sharkservers-api"
authors = ["Qwizi <ciolek.adrian@protonmail.com>"]
license = "GPL-3.0"
Expand Down Expand Up @@ -44,6 +44,7 @@ pillow = "^10.2.0"
broadcaster = "^0.2.0"
asyncio-redis = "^0.16.0"
stripe = "^7.12.0"
resend = "^0.7.2"

[tool.poetry.group.dev-skeleton.dependencies]
# This dependency group was generated from bswck/skeleton@57cf553.
Expand Down
7 changes: 5 additions & 2 deletions sharkservers/auth/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ class PasswordSchema(BaseModel):

@validator("password2")
def passwords_match(
cls, value, values, **kwargs
) -> None: # noqa: ANN001, N805, ARG002, ANN003
self,
value: str,
values: dict[str],
**kwargs: dict[str], # noqa: ARG002
) -> None:
"""
Check if the 'password2' field matches the 'password' field.
Expand Down
4 changes: 2 additions & 2 deletions sharkservers/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@

settings = get_settings()
limiter = RateLimiter(
times=999 if settings.TESTING else 3,
minutes=60 if settings.TESTING else 2,
times=999 if settings.TESTING else 10,
minutes=60 if settings.TESTING else 5,
)
refresh_token_limiter = RateLimiter(times=3, minutes=60)

Expand Down
11 changes: 7 additions & 4 deletions sharkservers/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Dependencies for the application."""
import resend
from fastapi import Depends
from fastapi_mail import ConnectionConfig, FastMail
from fastapi_mail import ConnectionConfig
from fastapi_mail.email_utils import DefaultChecker

from sharkservers.services import EmailService, UploadService
Expand Down Expand Up @@ -38,21 +39,23 @@ async def get_email_checker() -> DefaultChecker:


async def get_email_service(
settings: Settings = Depends(get_settings),
checker: DefaultChecker = Depends(get_email_checker),
) -> EmailService:
"""
Retrieve the email service.
Args:
----
checker (DefaultChecker, optional): The email checker. Defaults to Depends(get_email_checker).
settings (Settings, optional): The application settings. Defaults to Depends(get_settings).
checker (DefaultChecker, optional): The default email checker. Defaults to Depends(get_email_checker).
Returns:
-------
EmailService: The email service.
"""
mailer = FastMail(conf)
return EmailService(_mailer=mailer, checker=checker)
resend.api_key = settings.RESEND_API_KEY
return EmailService(resend=resend, checker=checker)


async def get_upload_service(
Expand Down
93 changes: 93 additions & 0 deletions sharkservers/exception_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Exception handlers for FastAPI."""
from __future__ import annotations

import sys
from typing import Union

from fastapi import Request
from fastapi.exception_handlers import http_exception_handler as _http_exception_handler
from fastapi.exception_handlers import (
request_validation_exception_handler as _request_validation_exception_handler,
)
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.responses import JSONResponse, PlainTextResponse, Response

from sharkservers.logger import logger


async def request_validation_exception_handler(
request: Request, exc: RequestValidationError
) -> JSONResponse:
"""
Middleware will log all RequestValidationErrors.
Args:
----
request: The request object.
exc: The RequestValidationError exception.
Returns:
-------
A JSONResponse with the errors and the request body.
"""
logger.debug("Our custom request_validation_exception_handler was called")
body = await request.body()
query_params = request.query_params._dict # pylint: disable=protected-access
detail = {
"errors": exc.errors(),
"body": body.decode(),
"query_params": query_params,
}
logger.info(detail)
return await _request_validation_exception_handler(request, exc)


async def http_exception_handler(
request: Request, exc: HTTPException
) -> Union[JSONResponse, Response]:
"""
HTTPException handler.
Args:
----
request: The request object.
exc: The HTTPException exception.
Returns:
-------
A JSONResponse with the error.
"""
logger.error(f"{exc.detail} <{exc.detail.value}>")
return await _http_exception_handler(request, exc)


async def unhandled_exception_handler(
request: Request, exc: Exception
) -> PlainTextResponse:
"""
Unhandled exception handler.
Args:
----
request: The request object.
exc: The exception.
Returns:
-------
A PlainTextResponse with the error.
"""
logger.debug("Our custom unhandled_exception_handler was called")
host = getattr(getattr(request, "client", None), "host", None)
port = getattr(getattr(request, "client", None), "port", None)
url = (
f"{request.url.path}?{request.query_params}"
if request.query_params
else request.url.path
)
exception_type, exception_value, exception_traceback = sys.exc_info()
exception_name = getattr(exception_type, "__name__", None)
logger.error(
f'{host}:{port} - "{request.method} {url}" 500 Internal Server Error <{exception_name}: {exception_value}>',
)
return PlainTextResponse(str(exc), status_code=500)
38 changes: 5 additions & 33 deletions sharkservers/logger.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,13 @@
"""Logging configuration for the server."""
import logging
from logging.config import dictConfig

from pydantic import BaseModel
# Disable uvicorn access logger
uvicorn_access = logging.getLogger("uvicorn.access")
uvicorn_access.disabled = True

logger = logging.getLogger("uvicorn")

class LogConfig(BaseModel):
"""Logging configuration to be set for the server."""

LOGGER_NAME: str = "mycoolapp"
LOG_FORMAT: str = "%(levelprefix)s | %(asctime)s | %(message)s"
LOG_LEVEL: str = "DEBUG"

# Logging config
version = 1
disable_existing_loggers = False
formatters = {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
}
handlers = {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
}
loggers = {
"mycoolapp": {"handlers": ["default"], "level": LOG_LEVEL},
}


dictConfig(LogConfig().dict())
logger = logging.getLogger("mycoolapp")
logger.setLevel(logging.getLevelName(logging.DEBUG))


def logger_with_filename(filename: str, data: str) -> None:
Expand Down
13 changes: 13 additions & 0 deletions sharkservers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
WebSocket,
WebSocketDisconnect,
)
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.gzip import GZipMiddleware
from fastapi_events.handlers.local import local_handler
from fastapi_events.middleware import EventHandlerASGIMiddleware
Expand All @@ -37,6 +38,11 @@
from sharkservers.chat.services import ChatService
from sharkservers.chat.views import router as chat_router
from sharkservers.chat.websocket import chatroom_ws_receiver, chatroom_ws_sender
from sharkservers.exception_handlers import (
http_exception_handler,
request_validation_exception_handler,
unhandled_exception_handler,
)
from sharkservers.forum.dependencies import (
get_categories_service,
get_posts_service,
Expand All @@ -51,6 +57,7 @@

# import admin posts router
from sharkservers.logger import logger
from sharkservers.middleware import log_request_middleware
from sharkservers.players.views import router as steamprofile_router
from sharkservers.players.views_admin import router as admin_steamprofiles_router
from sharkservers.roles.views import router as roles_router
Expand Down Expand Up @@ -240,6 +247,7 @@ async def update_user_last_online_time_middleware(
handlers=[local_handler],
middleware_id=event_handler_id,
)
_app.middleware("http")(log_request_middleware)
return _app


Expand All @@ -262,6 +270,11 @@ def create_app() -> FastAPI:
_app.mount("/static", StaticFiles(directory=st_abs_file_path), name="static")
init_routes(_app)
add_pagination(_app)
_app.add_exception_handler(
RequestValidationError, request_validation_exception_handler
)
_app.add_exception_handler(HTTPException, http_exception_handler)
_app.add_exception_handler(Exception, unhandled_exception_handler)

@_app.websocket("/ws")
async def websocket_endpoint(
Expand Down
34 changes: 34 additions & 0 deletions sharkservers/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import http
import time

from fastapi import Request

from sharkservers.logger import logger


async def log_request_middleware(request: Request, call_next):
"""
This middleware will log all requests and their processing time.
E.g. log:
0.0.0.0:1234 - GET /ping 200 OK 1.00ms
"""
logger.debug("middleware: log_request_middleware")
url = (
f"{request.url.path}?{request.query_params}"
if request.query_params
else request.url.path
)
start_time = time.time()
response = await call_next(request)
process_time = (time.time() - start_time) * 1000
formatted_process_time = f"{process_time:.2f}"
host = getattr(getattr(request, "client", None), "host", None)
port = getattr(getattr(request, "client", None), "port", None)
try:
status_phrase = http.HTTPStatus(response.status_code).phrase
except ValueError:
status_phrase = ""
logger.info(
f'{host}:{port} - "{request.method} {url}" {response.status_code} {status_phrase} {formatted_process_time}ms'
)
return response
Loading

0 comments on commit cc2a340

Please # to comment.