Initial check-in for notification - only structure
This commit is contained in:
parent
0a0c0d128d
commit
c0986b10bf
0
app/notification/Dockerfile
Normal file
0
app/notification/Dockerfile
Normal file
11
app/notification/backend/application/notification_hub.py
Normal file
11
app/notification/backend/application/notification_hub.py
Normal file
@ -0,0 +1,11 @@
|
||||
from app.notification.backend.business.notification_manager import (
|
||||
NotificationManager,
|
||||
)
|
||||
|
||||
|
||||
class NotificationHub:
|
||||
def __init__(
|
||||
self,
|
||||
):
|
||||
self.notification_manager = NotificationManager()
|
||||
return
|
||||
10
app/notification/backend/business/notification_manager.py
Normal file
10
app/notification/backend/business/notification_manager.py
Normal 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()
|
||||
2
app/notification/backend/infra/email_handler.py
Normal file
2
app/notification/backend/infra/email_handler.py
Normal file
@ -0,0 +1,2 @@
|
||||
class EmailHandler:
|
||||
pass
|
||||
2
app/notification/backend/infra/in_app_notif_handler.py
Normal file
2
app/notification/backend/infra/in_app_notif_handler.py
Normal file
@ -0,0 +1,2 @@
|
||||
class InAppNotifHandler:
|
||||
pass
|
||||
2
app/notification/backend/infra/sms_handler.py
Normal file
2
app/notification/backend/infra/sms_handler.py
Normal file
@ -0,0 +1,2 @@
|
||||
class SmsHandler:
|
||||
pass
|
||||
0
app/notification/backend/models/models.py
Normal file
0
app/notification/backend/models/models.py
Normal file
1
app/notification/backend/readme.md
Normal file
1
app/notification/backend/readme.md
Normal file
@ -0,0 +1 @@
|
||||
You can put code for backend logic here.
|
||||
2
app/notification/backend/services/email_service.py
Normal file
2
app/notification/backend/services/email_service.py
Normal file
@ -0,0 +1,2 @@
|
||||
class EmailService:
|
||||
pass
|
||||
@ -0,0 +1,2 @@
|
||||
class InAppNotifService:
|
||||
pass
|
||||
2
app/notification/backend/services/sms_service.py
Normal file
2
app/notification/backend/services/sms_service.py
Normal file
@ -0,0 +1,2 @@
|
||||
class SmsService:
|
||||
pass
|
||||
18
app/notification/common/config/app_settings.py
Normal file
18
app/notification/common/config/app_settings.py
Normal 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()
|
||||
14
app/notification/common/config/log_settings.py
Normal file
14
app/notification/common/config/log_settings.py
Normal 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()
|
||||
0
app/notification/requirements.txt
Normal file
0
app/notification/requirements.txt
Normal file
75
app/notification/webapi/bootstrap/application.py
Normal file
75
app/notification/webapi/bootstrap/application.py
Normal 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)
|
||||
6
app/notification/webapi/bootstrap/freeleaps_app.py
Normal file
6
app/notification/webapi/bootstrap/freeleaps_app.py
Normal file
@ -0,0 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
class FreeleapsApp(FastAPI):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
25
app/notification/webapi/config/site_settings.py
Normal file
25
app/notification/webapi/config/site_settings.py
Normal 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
30
app/notification/webapi/main.py
Executable 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 {}
|
||||
31
app/notification/webapi/providers/common.py
Normal file
31
app/notification/webapi/providers/common.py
Normal 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=["*"],
|
||||
)
|
||||
17
app/notification/webapi/providers/database.py
Normal file
17
app/notification/webapi/providers/database.py
Normal 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
|
||||
39
app/notification/webapi/providers/exception_handler.py
Normal file
39
app/notification/webapi/providers/exception_handler.py
Normal 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)
|
||||
60
app/notification/webapi/providers/logger.py
Normal file
60
app/notification/webapi/providers/logger.py
Normal 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()
|
||||
)
|
||||
34
app/notification/webapi/providers/router.py
Normal file
34
app/notification/webapi/providers/router.py
Normal 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",
|
||||
}
|
||||
)
|
||||
8
app/notification/webapi/providers/scheduler.py
Normal file
8
app/notification/webapi/providers/scheduler.py
Normal file
@ -0,0 +1,8 @@
|
||||
import asyncio
|
||||
|
||||
|
||||
def register(app):
|
||||
@app.on_event("startup")
|
||||
async def start_scheduler():
|
||||
#create your scheduler here
|
||||
pass
|
||||
5
app/notification/webapi/routes/__init__.py
Normal file
5
app/notification/webapi/routes/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
websocket_router = APIRouter()
|
||||
15
app/notification/webapi/routes/api.py
Normal file
15
app/notification/webapi/routes/api.py
Normal 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'
|
||||
@ -1 +1,2 @@
|
||||
export AZURE_STORAGE_DOCUMENT_API_KEY=xbiFtFeQ6v5dozgVM99fZ9huUomL7QcLu6s0y8zYHtIXZ8XdneKDMcg4liQr/9oNlVoRFcZhWjLY+ASt9cjICQ==
|
||||
export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0
|
||||
@ -1 +1,2 @@
|
||||
export AZURE_STORAGE_DOCUMENT_API_KEY=xbiFtFeQ6v5dozgVM99fZ9huUomL7QcLu6s0y8zYHtIXZ8XdneKDMcg4liQr/9oNlVoRFcZhWjLY+ASt9cjICQ==
|
||||
export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0
|
||||
5
sites/notification/.env
Normal file
5
sites/notification/.env
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user