Initial check-in for notification - only structure

This commit is contained in:
jetli 2024-10-24 05:07:12 +00:00
parent 0a0c0d128d
commit c0986b10bf
29 changed files with 420 additions and 2 deletions

View File

View File

@ -0,0 +1,11 @@
from app.notification.backend.business.notification_manager import (
NotificationManager,
)
class NotificationHub:
def __init__(
self,
):
self.notification_manager = NotificationManager()
return

View File

@ -0,0 +1,10 @@
from app.notification.backend.services.sms_service import SmsService
from app.notification.backend.services.in_app_notif_service import InAppNotifService
from app.notification.backend.services.email_service import EmailService
class NotificationManager:
def __init__(self) -> None:
self.sms_service = SmsService()
self.in_app_notif_service = InAppNotifService()
self.email_service = EmailService()

View File

@ -0,0 +1,2 @@
class EmailHandler:
pass

View File

@ -0,0 +1,2 @@
class InAppNotifHandler:
pass

View File

@ -0,0 +1,2 @@
class SmsHandler:
pass

View File

@ -0,0 +1 @@
You can put code for backend logic here.

View File

@ -0,0 +1,2 @@
class EmailService:
pass

View File

@ -0,0 +1,2 @@
class InAppNotifService:
pass

View File

@ -0,0 +1,2 @@
class SmsService:
pass

View File

@ -0,0 +1,18 @@
import os
from pydantic_settings import BaseSettings
class AppSettings(BaseSettings):
NAME: str = "notification"
AZURE_STORAGE_DOCUMENT_API_ENDPOINT: str = (
"https://freeleaps1document.blob.core.windows.net/"
)
AZURE_STORAGE_DOCUMENT_API_KEY: str = ""
class Config:
env_file = ".myapp.env"
env_file_encoding = "utf-8"
app_settings = AppSettings()

View File

@ -0,0 +1,14 @@
## This would be the app specific log setting file
class LogSettings:
LOG_LEVEL: str = "DEBUG"
LOG_PATH_BASE: str = "./logs"
LOG_PATH: str = LOG_PATH_BASE + "/" + "app" + ".log"
LOG_RETENTION: str = "14 days"
LOG_ROTATION: str = "00:00" # mid night
class Config:
env_file = ".log.env"
env_file_encoding = "utf-8"
log_settings = LogSettings()

View File

View File

@ -0,0 +1,75 @@
import logging
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from webapi.providers import common
from webapi.providers import logger
from webapi.providers import router
from webapi.providers import database
from webapi.providers import scheduler
from webapi.providers import exception_handler
from .freeleaps_app import FreeleapsApp
def create_app() -> FastAPI:
logging.info("App initializing")
app = FreeleapsApp()
register(app, exception_handler)
register(app, database)
register(app, logger)
register(app, router)
register(app, scheduler)
register(app, common)
# Call the custom_openapi function to change the OpenAPI version
customize_openapi_security(app)
return app
# This function overrides the OpenAPI schema version to 3.0.0
def customize_openapi_security(app: FastAPI) -> None:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
# Generate OpenAPI schema
openapi_schema = get_openapi(
title="FreeLeaps API",
version="3.1.0",
description="FreeLeaps API Documentation",
routes=app.routes,
)
# Ensure the components section exists in the OpenAPI schema
if "components" not in openapi_schema:
openapi_schema["components"] = {}
# Add security scheme to components
openapi_schema["components"]["securitySchemes"] = {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
# Add security requirement globally
openapi_schema["security"] = [{"bearerAuth": []}]
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
def register(app, provider):
logging.info(provider.__name__ + " registering")
provider.register(app)
def boot(app, provider):
logging.info(provider.__name__ + " booting")
provider.boot(app)

View File

@ -0,0 +1,6 @@
from fastapi import FastAPI
class FreeleapsApp(FastAPI):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -0,0 +1,25 @@
import os
from pydantic_settings import BaseSettings
class SiteSettings(BaseSettings):
NAME: str = "appname"
DEBUG: bool = True
ENV: str = "dev"
SERVER_HOST: str = "0.0.0.0"
SERVER_PORT: int = 8103
URL: str = "http://localhost"
TIME_ZONE: str = "UTC"
BASE_PATH: str = os.path.dirname(os.path.dirname((os.path.abspath(__file__))))
class Config:
env_file = ".devbase-webapi.env"
env_file_encoding = "utf-8"
site_settings = SiteSettings()

30
app/notification/webapi/main.py Executable file
View File

@ -0,0 +1,30 @@
from webapi.bootstrap.application import create_app
from webapi.config.site_settings import site_settings
from fastapi.responses import RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
from strawberry.fastapi import GraphQLRouter
from strawberry.fastapi.handlers import GraphQLTransportWSHandler, GraphQLWSHandler
import uvicorn
from typing import Any
app = create_app()
@app.get("/", status_code=301)
async def root():
"""
TODO: redirect client to /doc#
"""
return RedirectResponse("docs")
if __name__ == "__main__":
uvicorn.run(app="main:app", host=site_settings.SERVER_HOST, port=site_settings.SERVER_PORT)
def get_context() -> Any:
# Define your context function. This is where you can set up authentication, database connections, etc.
return {}
def get_root_value() -> Any:
# Define your root value function. This can be used to customize the root value for GraphQL operations.
return {}

View File

@ -0,0 +1,31 @@
from fastapi.middleware.cors import CORSMiddleware
from webapi.config.site_settings import site_settings
def register(app):
app.debug = site_settings.DEBUG
app.title = site_settings.NAME
add_global_middleware(app)
# This hook ensures that a connection is opened to handle any queries
# generated by the request.
@app.on_event("startup")
def startup():
pass
# This hook ensures that the connection is closed when we've finished
# processing the request.
@app.on_event("shutdown")
def shutdown():
pass
def add_global_middleware(app):
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

View File

@ -0,0 +1,17 @@
from webapi.config.site_settings import site_settings
from beanie import init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
def register(app):
app.debug = site_settings.DEBUG
app.title = site_settings.NAME
@app.on_event("startup")
async def start_database():
await initiate_database()
async def initiate_database():
#init your database here
pass

View File

@ -0,0 +1,39 @@
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import (
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_422_UNPROCESSABLE_ENTITY,
HTTP_500_INTERNAL_SERVER_ERROR,
)
async def custom_http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"error": exc.detail},
)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=HTTP_400_BAD_REQUEST,
content={"error": str(exc)},
)
async def exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=HTTP_500_INTERNAL_SERVER_ERROR,
content={"error": str(exc)},
)
def register(app: FastAPI):
app.add_exception_handler(HTTPException, custom_http_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(Exception, exception_handler)

View File

@ -0,0 +1,60 @@
import logging
import sys
from loguru import logger
from common.config.log_settings import log_settings
def register(app=None):
level = log_settings.LOG_LEVEL
file_path = log_settings.LOG_PATH
retention = log_settings.LOG_RETENTION
rotation = log_settings.LOG_ROTATION
# intercept everything at the root logger
logging.root.handlers = [InterceptHandler()]
logging.root.setLevel(level)
# remove every other logger's handlers
# and propagate to root logger
for name in logging.root.manager.loggerDict.keys():
logging.getLogger(name).handlers = []
logging.getLogger(name).propagate = True
# configure loguru
logger.add(
sink=sys.stdout
)
logger.add(
sink=file_path,
level=level,
retention=retention,
rotation=rotation
)
logger.disable("pika.adapters")
logger.disable("pika.connection")
logger.disable("pika.channel")
logger.disable("pika.callback")
logger.disable("pika.frame")
logger.disable("pika.spec")
logger.disable("aiormq.connection")
logger.disable("urllib3.connectionpool")
class InterceptHandler(logging.Handler):
def emit(self, record):
# Get corresponding Loguru level if it exists
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
level, record.getMessage()
)

View File

@ -0,0 +1,34 @@
from webapi.routes import api_router
from starlette import routing
def register(app):
app.include_router(
api_router,
prefix="/api",
tags=["api"],
dependencies=[],
responses={404: {"description": "no page found"}},
)
if app.debug:
for route in app.routes:
if not isinstance(route, routing.WebSocketRoute):
print(
{
"path": route.path,
"endpoint": route.endpoint,
"name": route.name,
"methods": route.methods,
}
)
else:
print(
{
"path": route.path,
"endpoint": route.endpoint,
"name": route.name,
"type": "web socket route",
}
)

View File

@ -0,0 +1,8 @@
import asyncio
def register(app):
@app.on_event("startup")
async def start_scheduler():
#create your scheduler here
pass

View File

@ -0,0 +1,5 @@
from fastapi import APIRouter
api_router = APIRouter()
websocket_router = APIRouter()

View File

@ -0,0 +1,15 @@
from fastapi.routing import APIRoute
from starlette import routing
def post_process_router(app) -> None:
"""
Simplify operation IDs so that generated API clients have simpler function
names.
Should be called only after all routes have been added.
"""
for route in app.routes:
if isinstance(route, APIRoute):
if hasattr(route, "operation_id"):
route.operation_id = route.name # in this case, 'read_items'

View File

@ -1 +1,2 @@
export AZURE_STORAGE_DOCUMENT_API_KEY=xbiFtFeQ6v5dozgVM99fZ9huUomL7QcLu6s0y8zYHtIXZ8XdneKDMcg4liQr/9oNlVoRFcZhWjLY+ASt9cjICQ==
export AZURE_STORAGE_DOCUMENT_API_KEY=xbiFtFeQ6v5dozgVM99fZ9huUomL7QcLu6s0y8zYHtIXZ8XdneKDMcg4liQr/9oNlVoRFcZhWjLY+ASt9cjICQ==
export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0

View File

@ -1 +1,2 @@
export AZURE_STORAGE_DOCUMENT_API_KEY=xbiFtFeQ6v5dozgVM99fZ9huUomL7QcLu6s0y8zYHtIXZ8XdneKDMcg4liQr/9oNlVoRFcZhWjLY+ASt9cjICQ==
export AZURE_STORAGE_DOCUMENT_API_KEY=xbiFtFeQ6v5dozgVM99fZ9huUomL7QcLu6s0y8zYHtIXZ8XdneKDMcg4liQr/9oNlVoRFcZhWjLY+ASt9cjICQ==
export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0

5
sites/notification/.env Normal file
View File

@ -0,0 +1,5 @@
export SENDGRID_API_KEY='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'
export EMAIL_FROM=freeleaps@freeleaps.com
export TWILIO_ACCOUNT_SID=ACf8c9283a6acda060258eadb29be58bc8
export TWILIO_AUTH_TOKEN=ef160748cc22c8b7195b49df4b8eca7e
export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0