update
This commit is contained in:
parent
234c5a491f
commit
ee5e80acd9
@ -12,8 +12,9 @@ class AppSettings(BaseSettings):
|
||||
MONGODB_URI:str= ""
|
||||
MONGODB_NAME:str= ""
|
||||
|
||||
LOG_BASE_PATH : str = "./log"
|
||||
BACKEND_LOG_FILE_NAME: str = APP_NAME
|
||||
APPLICATION_ACTIVITY_LOG: str = APP_NAME + "-application-activity"
|
||||
BUSINESS_METRIC_LOG: str = APP_NAME + "-business-metrics"
|
||||
|
||||
class Config:
|
||||
env_file = ".myapp.env"
|
||||
|
||||
@ -4,11 +4,16 @@ from pydantic_settings import BaseSettings
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
NAME: str = "central_storage"
|
||||
|
||||
APP_NAME:str = NAME
|
||||
|
||||
AZURE_STORAGE_DOCUMENT_API_ENDPOINT: str = ""
|
||||
|
||||
AZURE_STORAGE_DOCUMENT_API_KEY: str = ""
|
||||
|
||||
LOG_BASE_PATH : str = "./log"
|
||||
BACKEND_LOG_FILE_NAME: str = APP_NAME
|
||||
APPLICATION_ACTIVITY_LOG: str = APP_NAME + "-application-activity"
|
||||
|
||||
class Config:
|
||||
env_file = ".myapp.env"
|
||||
env_file_encoding = "utf-8"
|
||||
|
||||
@ -9,8 +9,9 @@ class AppSettings():
|
||||
|
||||
CENTRAL_STORAGE_WEBAPI_URL_BASE:str =""
|
||||
|
||||
LOG_BASE_PATH : str = "./log"
|
||||
BACKEND_LOG_FILE_NAME: str = APP_NAME
|
||||
APPLICATION_ACTIVITY_LOG: str = APP_NAME + "-application-activity"
|
||||
BUSINESS_METRIC_LOG: str = APP_NAME + "-business-metrics"
|
||||
|
||||
class Config:
|
||||
env_file = ".content.env"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from typing import Dict
|
||||
from app.notification.backend.business.notification_manager import NotificationManager
|
||||
from app.notification.backend.models.constants import NotificationChannel
|
||||
from backend.business.notification_manager import NotificationManager
|
||||
from backend.models.constants import NotificationChannel
|
||||
|
||||
|
||||
class NotificationHub:
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
from typing import Dict
|
||||
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
|
||||
from app.notification.backend.services.notification_publisher_service import (
|
||||
from backend.services.sms_service import SmsService
|
||||
from backend.services.in_app_notif_service import InAppNotifService
|
||||
from backend.services.email_service import EmailService
|
||||
from backend.services.notification_publisher_service import (
|
||||
NotificationPublisherService,
|
||||
)
|
||||
from app.notification.backend.models.constants import (
|
||||
from backend.models.constants import (
|
||||
NotificationChannel,
|
||||
NotificationMessage,
|
||||
SystemNotifications,
|
||||
@ -14,8 +14,8 @@ import threading
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, Type
|
||||
from types import TracebackType
|
||||
from infra.models.constants import UserRegion
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from common.constants.region import UserRegion
|
||||
from common.config.app_settings import app_settings
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from common.config.app_settings import app_settings
|
||||
from sendgrid import SendGridAPIClient
|
||||
from sendgrid.helpers.mail import Mail
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from common.log.module_logger import ModuleLogger
|
||||
|
||||
|
||||
class EmailHandler:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from common.config.app_settings import app_settings
|
||||
from common.log.module_logger import ModuleLogger
|
||||
import asyncio
|
||||
from asyncio import AbstractEventLoop
|
||||
import aio_pika
|
||||
@ -17,7 +17,7 @@ class AsyncMQClient:
|
||||
self.process_callable = None
|
||||
self.routing_key = self.channel_name
|
||||
self.module_logger = ModuleLogger(sender_id="AsyncMQClient")
|
||||
|
||||
self.connection_url = "amqp://guest:guest@{}:{}/".format(app_settings.RABBITMQ_HOST,app_settings.RABBITMQ_PORT)
|
||||
async def bind(self, max_retries=10, event_loop: AbstractEventLoop = None):
|
||||
retry_count = 0
|
||||
retry_interval = 1 # Start with a 1-second interval
|
||||
@ -25,7 +25,7 @@ class AsyncMQClient:
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
self.connection = await aio_pika.connect_robust(
|
||||
"amqp://guest:guest@rabbitmq:5672/",
|
||||
self.connection_url,
|
||||
loop=event_loop,
|
||||
)
|
||||
self.channel = await self.connection.channel()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from common.log.module_logger import ModuleLogger
|
||||
from .async_client import AsyncMQClient
|
||||
import aio_pika
|
||||
import json
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from asyncio import AbstractEventLoop
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from common.log.module_logger import ModuleLogger
|
||||
import json
|
||||
import asyncio
|
||||
from .async_client import AsyncMQClient
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from common.config.app_settings import app_settings
|
||||
from twilio.http.async_http_client import AsyncTwilioHttpClient
|
||||
from twilio.rest import Client
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel
|
||||
from infra.models.constants import UserRegion
|
||||
from common.constants.region import UserRegion
|
||||
|
||||
|
||||
class NotificationChannel(Enum):
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from app.notification.backend.models.constants import NotificationChannel
|
||||
from app.notification.backend.infra.rabbitmq.async_publisher import AsyncMQPublisher
|
||||
from backend.models.constants import NotificationChannel
|
||||
from backend.infra.rabbitmq.async_publisher import AsyncMQPublisher
|
||||
|
||||
|
||||
class NotificationPublisherService:
|
||||
|
||||
@ -4,22 +4,26 @@ from pydantic_settings import BaseSettings
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
NAME: str = "notification"
|
||||
|
||||
RABBITMQ_HOST: str = "rabbitmq"
|
||||
APP_NAME:str = NAME
|
||||
|
||||
RABBITMQ_HOST: str = ""
|
||||
RABBITMQ_PORT: int = 5672
|
||||
|
||||
SYSTEM_USER_ID: str = "117f191e810c19729de860aa"
|
||||
SMS_FROM: str = "+16898887156"
|
||||
EMAIL_FROM: str = "freeleaps@freeleaps.com"
|
||||
SYSTEM_USER_ID: str = ""
|
||||
SMS_FROM: str = ""
|
||||
EMAIL_FROM: str = ""
|
||||
|
||||
SECRET_KEY: str = ""
|
||||
|
||||
SENDGRID_API_KEY: str = (
|
||||
"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"
|
||||
)
|
||||
SENDGRID_API_KEY: str = ""
|
||||
|
||||
|
||||
TWILIO_ACCOUNT_SID: str = "ACf8c9283a6acda060258eadb29be58bc8"
|
||||
TWILIO_AUTH_TOKEN: str = "ef160748cc22c8b7195b49df4b8eca7e"
|
||||
TWILIO_ACCOUNT_SID: str = ""
|
||||
TWILIO_AUTH_TOKEN: str = ""
|
||||
|
||||
LOG_BASE_PATH : str = "./log"
|
||||
BACKEND_LOG_FILE_NAME: str = APP_NAME
|
||||
APPLICATION_ACTIVITY_LOG: str = APP_NAME + "-application-activity"
|
||||
|
||||
class Config:
|
||||
env_file = ".myapp.env"
|
||||
|
||||
0
apps/notification/common/constants/__init__.py
Normal file
0
apps/notification/common/constants/__init__.py
Normal file
8
apps/notification/common/constants/contract.py
Normal file
8
apps/notification/common/constants/contract.py
Normal file
@ -0,0 +1,8 @@
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class UserContractRole(IntEnum):
|
||||
VISITER = 0x1
|
||||
REQUESTER = 0x2
|
||||
PROVIDER = 0x4
|
||||
|
||||
5
apps/notification/common/constants/payment.py
Normal file
5
apps/notification/common/constants/payment.py
Normal file
@ -0,0 +1,5 @@
|
||||
from enum import IntEnum
|
||||
|
||||
class PaymentPlanType(IntEnum):
|
||||
STAGED = 0
|
||||
|
||||
5
apps/notification/common/constants/region.py
Normal file
5
apps/notification/common/constants/region.py
Normal file
@ -0,0 +1,5 @@
|
||||
from enum import IntEnum
|
||||
|
||||
class UserRegion(IntEnum):
|
||||
OTHER = 0
|
||||
ZH_CN = 1
|
||||
0
apps/notification/common/exception/__init__.py
Normal file
0
apps/notification/common/exception/__init__.py
Normal file
23
apps/notification/common/exception/exceptions.py
Normal file
23
apps/notification/common/exception/exceptions.py
Normal file
@ -0,0 +1,23 @@
|
||||
class DoesNotExistError(Exception):
|
||||
def __init__(self, message: str = "Does Not Exist"):
|
||||
self.message = message
|
||||
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
def __init__(self, message: str = "Unauthorized"):
|
||||
self.message = message
|
||||
|
||||
|
||||
class AuthorizationError(Exception):
|
||||
def __init__(self, message: str = "Forbidden"):
|
||||
self.message = message
|
||||
|
||||
|
||||
class InvalidOperationError(Exception):
|
||||
def __init__(self, message: str = "Invalid Operation"):
|
||||
self.message = message
|
||||
|
||||
|
||||
class InvalidDataError(Exception):
|
||||
def __init__(self, message: str = "Invalid Data"):
|
||||
self.message = message
|
||||
0
apps/notification/common/log/__init__.py
Normal file
0
apps/notification/common/log/__init__.py
Normal file
14
apps/notification/common/log/application_logger.py
Normal file
14
apps/notification/common/log/application_logger.py
Normal file
@ -0,0 +1,14 @@
|
||||
from .base_logger import LoggerBase
|
||||
from common.config.app_settings import app_settings
|
||||
import json
|
||||
|
||||
|
||||
class ApplicationLogger(LoggerBase):
|
||||
def __init__(self, application_activities: dict[str, any] = {}) -> None:
|
||||
extra_fileds = {}
|
||||
if application_activities:
|
||||
extra_fileds.update(application_activities)
|
||||
super().__init__(
|
||||
logger_name=app_settings.APPLICATION_ACTIVITY_LOG,
|
||||
extra_fileds=extra_fileds,
|
||||
)
|
||||
139
apps/notification/common/log/base_logger.py
Normal file
139
apps/notification/common/log/base_logger.py
Normal file
@ -0,0 +1,139 @@
|
||||
from loguru import logger as guru_logger
|
||||
from common.config.log_settings import log_settings
|
||||
from typing import List
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
|
||||
|
||||
class LoggerBase:
|
||||
binded_loggers = {}
|
||||
logger_lock = threading.Lock()
|
||||
|
||||
def __init__(
|
||||
self, logger_name: str, extra_fileds: dict[str, any]
|
||||
) -> None:
|
||||
self.__logger_name = logger_name
|
||||
self.extra_fileds = extra_fileds
|
||||
with LoggerBase.logger_lock:
|
||||
if self.__logger_name in LoggerBase.binded_loggers:
|
||||
self.logger = LoggerBase.binded_loggers[self.__logger_name]
|
||||
return
|
||||
|
||||
log_filename = (
|
||||
log_settings.LOG_BASE_PATH + "/" + self.__logger_name + ".log"
|
||||
)
|
||||
log_retention = log_settings.LOG_RETENTION
|
||||
log_rotation = log_settings.LOG_ROTATION
|
||||
log_level = "INFO"
|
||||
log_message_format = "{message}"
|
||||
|
||||
guru_logger.add(
|
||||
sink=log_filename,
|
||||
level=log_level,
|
||||
retention=log_retention,
|
||||
rotation=log_rotation,
|
||||
format=log_message_format,
|
||||
serialize=True,
|
||||
filter=lambda record: "extra" in record
|
||||
and "topic" in record["extra"]
|
||||
and record["extra"]["topic"] == self.__logger_name,
|
||||
)
|
||||
host_name = socket.gethostname()
|
||||
host_ip = socket.gethostbyname(host_name)
|
||||
self.logger = guru_logger.bind(
|
||||
topic=self.__logger_name,
|
||||
host_ip=host_ip,
|
||||
host_name=host_name,
|
||||
)
|
||||
with LoggerBase.logger_lock:
|
||||
LoggerBase.binded_loggers[self.__logger_name] = self.logger
|
||||
|
||||
async def log_event(
|
||||
self,
|
||||
sender_id: str,
|
||||
receiver_id: str,
|
||||
subject: str,
|
||||
event: str,
|
||||
properties: dict[str, any],
|
||||
text: str = ""
|
||||
) -> None:
|
||||
local_logger = self.logger.bind(
|
||||
sender_id=sender_id,
|
||||
receiver_id=receiver_id,
|
||||
subject=subject,
|
||||
event=event,
|
||||
properties=properties
|
||||
)
|
||||
local_logger.info(text)
|
||||
|
||||
async def log_exception(
|
||||
self,
|
||||
sender_id: str,
|
||||
receiver_id: str,
|
||||
subject: str,
|
||||
exception: Exception,
|
||||
text: str = "",
|
||||
properties: dict[str, any] = None,
|
||||
) -> None:
|
||||
local_logger = self.logger.bind(
|
||||
sender_id=sender_id,
|
||||
receiver_id=receiver_id,
|
||||
subject=subject,
|
||||
event="exception",
|
||||
properties=properties,
|
||||
exception=exception
|
||||
)
|
||||
local_logger.exception(text)
|
||||
|
||||
async def log_info(
|
||||
self,
|
||||
sender_id: str,
|
||||
receiver_id: str,
|
||||
subject: str,
|
||||
text: str = "",
|
||||
properties: dict[str, any] = None,
|
||||
) -> None:
|
||||
local_logger = self.logger.bind(
|
||||
sender_id=sender_id,
|
||||
receiver_id=receiver_id,
|
||||
subject=subject,
|
||||
event="information",
|
||||
properties=properties,
|
||||
)
|
||||
local_logger.info(text)
|
||||
|
||||
async def log_warning(
|
||||
self,
|
||||
sender_id: str,
|
||||
receiver_id: str,
|
||||
subject: str,
|
||||
text: str = "",
|
||||
properties: dict[str, any] = None,
|
||||
) -> None:
|
||||
local_logger = self.logger.bind(
|
||||
sender_id=sender_id,
|
||||
receiver_id=receiver_id,
|
||||
subject=subject,
|
||||
event="warning",
|
||||
properties=properties,
|
||||
)
|
||||
local_logger.warning(text)
|
||||
|
||||
async def log_error(
|
||||
self,
|
||||
sender_id: str,
|
||||
receiver_id: str,
|
||||
subject: str,
|
||||
text: str = "",
|
||||
properties: dict[str, any] = None,
|
||||
) -> None:
|
||||
local_logger = self.logger.bind(
|
||||
sender_id=sender_id,
|
||||
receiver_id=receiver_id,
|
||||
subject=subject,
|
||||
event="error",
|
||||
properties=properties,
|
||||
)
|
||||
local_logger.error(text)
|
||||
25
apps/notification/common/log/business_metric_logger.py
Normal file
25
apps/notification/common/log/business_metric_logger.py
Normal file
@ -0,0 +1,25 @@
|
||||
from .base_logger import LoggerBase
|
||||
from common.config.app_settings import app_settings
|
||||
import json
|
||||
|
||||
|
||||
class BusinessMetricLogger(LoggerBase):
|
||||
def __init__(self, business_metrics: dict[str, any] = {}) -> None:
|
||||
extra_fileds = {}
|
||||
if business_metrics:
|
||||
extra_fileds.update(business_metrics)
|
||||
super().__init__(
|
||||
logger_name=app_settings.BUSINESS_METRIC_LOG,
|
||||
extra_fileds=extra_fileds,
|
||||
)
|
||||
|
||||
|
||||
async def log_metrics(self, business_metrics: dict[str, any] = {}) -> None:
|
||||
return await super().log_event(
|
||||
sender_id="business_metric_manager",
|
||||
receiver_id="business_metric_logger",
|
||||
subject="metrics",
|
||||
event="logging",
|
||||
properties=business_metrics,
|
||||
text="business metric logged"
|
||||
)
|
||||
50
apps/notification/common/log/function_logger.py
Normal file
50
apps/notification/common/log/function_logger.py
Normal file
@ -0,0 +1,50 @@
|
||||
from .application_logger import ApplicationLogger
|
||||
|
||||
|
||||
class FunctionLogger(ApplicationLogger):
|
||||
def __init__(self, sender_id: str, receiver_id:str) -> None:
|
||||
super().__init__()
|
||||
self.event_sender_id = sender_id
|
||||
self.event_receiver_id = receiver_id
|
||||
self.event_subject = "function"
|
||||
|
||||
async def log_enter(self, function: str, file: str):
|
||||
return await super().log_event(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
event="enter",
|
||||
properties={
|
||||
"function": function,
|
||||
"file": file,
|
||||
},
|
||||
text="Enter:{} of {}".format(function, file)
|
||||
)
|
||||
|
||||
async def log_exit(self, function: str, file: str, excution_time_in_ns: int):
|
||||
return await super().log_event(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
event="exit",
|
||||
properties={
|
||||
"function": function,
|
||||
"file": file,
|
||||
"excution_time_in_ns": excution_time_in_ns
|
||||
},
|
||||
text="Exit:{} of {}".format(function, file)
|
||||
)
|
||||
|
||||
async def log_exception(self, exception: Exception, function: str, file: str, excution_time_in_ns: int) -> None:
|
||||
return await super().log_exception(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
exception=exception,
|
||||
text="Exception:{} of {}".format(function, file),
|
||||
properties={
|
||||
"function": function,
|
||||
"file": file,
|
||||
"excution_time_in_ns": excution_time_in_ns
|
||||
},
|
||||
)
|
||||
25
apps/notification/common/log/log_utils.py
Normal file
25
apps/notification/common/log/log_utils.py
Normal file
@ -0,0 +1,25 @@
|
||||
import os
|
||||
from .function_logger import FunctionLogger
|
||||
import time
|
||||
import functools
|
||||
|
||||
|
||||
def log_entry_exit_async(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
file_path = os.path.relpath(func.__code__.co_filename)
|
||||
function_logger = FunctionLogger(sender_id="log_entry_exit_async", receiver_id="function_logger")
|
||||
start_time = time.process_time_ns()
|
||||
try:
|
||||
await function_logger.log_enter(func.__name__, file_path)
|
||||
result = await func(*args, **kwargs)
|
||||
await function_logger.log_exit(func.__name__, file_path, time.process_time_ns() - start_time)
|
||||
return result
|
||||
except Exception as exception:
|
||||
await function_logger.log_exception(
|
||||
exception=exception,
|
||||
function=func.__name__,
|
||||
file=file_path,
|
||||
excution_time_in_ns=time.process_time_ns() - start_time)
|
||||
raise
|
||||
return wrapper
|
||||
46
apps/notification/common/log/module_logger.py
Normal file
46
apps/notification/common/log/module_logger.py
Normal file
@ -0,0 +1,46 @@
|
||||
from .application_logger import ApplicationLogger
|
||||
|
||||
|
||||
class ModuleLogger(ApplicationLogger):
|
||||
def __init__(self, sender_id: str) -> None:
|
||||
super().__init__()
|
||||
self.event_sender_id = sender_id
|
||||
self.event_receiver_id = "ModuleLogger"
|
||||
self.event_subject = "module"
|
||||
|
||||
async def log_exception(self, exception: Exception, text: str = "Exception", properties: dict[str, any] = None) -> None:
|
||||
return await super().log_exception(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
exception=exception,
|
||||
text=text,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
async def log_info(self, info: str, properties: dict[str, any] = None) -> None:
|
||||
return await super().log_info(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
text=info,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
async def log_warning(self, warning: str, properties: dict[str, any] = None) -> None:
|
||||
return await super().log_warning(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
text=warning,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
async def log_error(self, error: str, properties: dict[str, any] = None) -> None:
|
||||
return await super().log_error(
|
||||
sender_id=self.event_sender_id,
|
||||
receiver_id=self.event_receiver_id,
|
||||
subject=self.event_subject,
|
||||
text=error,
|
||||
properties=properties,
|
||||
)
|
||||
14
apps/notification/common/log/user_logger.py
Normal file
14
apps/notification/common/log/user_logger.py
Normal file
@ -0,0 +1,14 @@
|
||||
from .base_logger import LoggerBase
|
||||
from common.config.app_settings import app_settings
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class UserLogger(LoggerBase):
|
||||
def __init__(self, user_activities: dict[str, any] = {}) -> None:
|
||||
extra_fileds = {}
|
||||
if user_activities:
|
||||
extra_fileds.update(user_activities)
|
||||
super().__init__(
|
||||
logger_name=app_settings.USER_ACTIVITY_LOG, extra_fileds=extra_fileds
|
||||
)
|
||||
80
apps/notification/common/token/token_manager.py
Normal file
80
apps/notification/common/token/token_manager.py
Normal file
@ -0,0 +1,80 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict
|
||||
from jose import jwt, JWTError
|
||||
from common.config.app_settings import app_settings
|
||||
from fastapi import Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
class TokenManager:
|
||||
def __init__(self):
|
||||
self.secret_key = app_settings.JWT_SECRET_KEY
|
||||
self.algorithm = "HS256"
|
||||
self.access_token_expire_minutes = app_settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
self.refresh_token_expire_days = app_settings.REFRESH_TOKEN_EXPIRE_DAYS
|
||||
|
||||
def create_access_token(self, subject: Dict[str, str]) -> str:
|
||||
"""
|
||||
Generates an access token with a short expiration time.
|
||||
"""
|
||||
expire = datetime.now(timezone.utc) + timedelta(
|
||||
minutes=self.access_token_expire_minutes
|
||||
)
|
||||
to_encode = subject.copy()
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
|
||||
|
||||
def create_refresh_token(self, subject: Dict[str, str]) -> str:
|
||||
"""
|
||||
Generates a refresh token with a longer expiration time.
|
||||
"""
|
||||
expire = datetime.now(timezone.utc) + timedelta(
|
||||
days=self.refresh_token_expire_days
|
||||
)
|
||||
to_encode = subject.copy()
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
|
||||
|
||||
def decode_token(self, token: str) -> Dict:
|
||||
"""
|
||||
Decodes a JWT token and returns the payload.
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
||||
return payload
|
||||
except JWTError:
|
||||
raise ValueError("Invalid token")
|
||||
|
||||
def verify_refresh_token(self, token: str) -> bool:
|
||||
"""
|
||||
Verifies a refresh token to ensure it is valid and not expired.
|
||||
"""
|
||||
try:
|
||||
payload = self.decode_token(token)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def refresh_access_token(self, refresh_token: str, subject: Dict[str, str]) -> str:
|
||||
"""
|
||||
Verifies the refresh token and creates a new access token.
|
||||
"""
|
||||
if self.verify_refresh_token(refresh_token):
|
||||
return self.create_access_token(subject)
|
||||
else:
|
||||
raise ValueError("Invalid refresh token")
|
||||
|
||||
async def get_current_user(
|
||||
self, token: str = Depends(OAuth2PasswordBearer(tokenUrl="token"))
|
||||
) -> Dict:
|
||||
"""
|
||||
Extract and validate user information from the JWT token.
|
||||
"""
|
||||
try:
|
||||
payload = self.decode_token(token) # Decode JWT token
|
||||
return payload
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED, detail="Invalid or expired token"
|
||||
)
|
||||
22
apps/notification/common/utils/date.py
Normal file
22
apps/notification/common/utils/date.py
Normal file
@ -0,0 +1,22 @@
|
||||
import datetime
|
||||
from datetime import timedelta, timezone
|
||||
|
||||
|
||||
def get_sunday(date):
|
||||
return date - datetime.timedelta(days=date.weekday()) + timedelta(days=6)
|
||||
|
||||
|
||||
def get_last_sunday_dates(number, include_current_week=True):
|
||||
now_utc = datetime.datetime.now(timezone.utc)
|
||||
today = datetime.datetime(now_utc.year, now_utc.month, now_utc.day)
|
||||
if include_current_week:
|
||||
days_to_last_sunday = (6 - today.weekday()) % 7
|
||||
last_sunday = today + datetime.timedelta(days=days_to_last_sunday)
|
||||
else:
|
||||
days_to_last_sunday = (today.weekday() - 6) % 7
|
||||
last_sunday = today - datetime.timedelta(days=days_to_last_sunday)
|
||||
last_n_sundays = []
|
||||
for i in range(number):
|
||||
sunday = last_sunday - datetime.timedelta(days=i * 7)
|
||||
last_n_sundays.append(sunday.date())
|
||||
return last_n_sundays
|
||||
13
apps/notification/common/utils/region.py
Normal file
13
apps/notification/common/utils/region.py
Normal file
@ -0,0 +1,13 @@
|
||||
from common.constants.region import UserRegion
|
||||
|
||||
|
||||
class RegionHandler:
|
||||
def __init__(self):
|
||||
self._zh_cn_patterns = [".cn", "cn.", "host"]
|
||||
|
||||
def detect_from_host(self, host: str) -> UserRegion:
|
||||
# Now we set user preferred region based on host
|
||||
for parttern in self._zh_cn_patterns:
|
||||
if parttern in host.lower():
|
||||
return UserRegion.ZH_CN
|
||||
return UserRegion.OTHER
|
||||
87
apps/notification/common/utils/string.py
Normal file
87
apps/notification/common/utils/string.py
Normal file
@ -0,0 +1,87 @@
|
||||
import random
|
||||
import re
|
||||
import jieba
|
||||
from typing import List
|
||||
|
||||
SKILL_TAGS = [
|
||||
"C++",
|
||||
"Java",
|
||||
"Python",
|
||||
"TypeScript",
|
||||
"iOS",
|
||||
"Android",
|
||||
"Web",
|
||||
"Javascript",
|
||||
"Vue",
|
||||
"Go",
|
||||
]
|
||||
|
||||
# dynamically update skill tags? maybe based on the most commonly extracted keywords to help the system adapt to change
|
||||
def updateSkillTags(string):
|
||||
SKILL_TAGS.append(string)
|
||||
|
||||
|
||||
def generate_auth_code():
|
||||
filtered = "0123456789"
|
||||
code = "".join(random.choice(filtered) for i in range(6))
|
||||
return code
|
||||
|
||||
|
||||
# TODO: Need to optimize
|
||||
def generate_self_intro_summary(content_html: str) -> str:
|
||||
element_html = re.compile("<.*?>")
|
||||
content_text = re.sub(element_html, "", content_html).strip()
|
||||
return content_text[:50]
|
||||
|
||||
|
||||
# TODO: Need to optimize
|
||||
def extract_skill_tags(content_html: str) -> List[str]:
|
||||
element_html = re.compile("<.*?>")
|
||||
content_text = re.sub(element_html, "", content_html).strip()
|
||||
words = set([word.lower() for word in jieba.cut(content_text) if word.strip()])
|
||||
|
||||
results = []
|
||||
for tag in SKILL_TAGS:
|
||||
if tag.lower() in words:
|
||||
results.append(tag)
|
||||
return results
|
||||
|
||||
|
||||
def extract_title(content_html: str) -> List[str]:
|
||||
element_html = re.compile("<.*?>")
|
||||
content_text = re.sub(element_html, "\n", content_html).strip()
|
||||
|
||||
cut_point_indexes = []
|
||||
for cut_point in [".", ",", ";", "\r", "\n"]:
|
||||
result = content_text.find(cut_point)
|
||||
if result > 0:
|
||||
cut_point_indexes.append(result)
|
||||
|
||||
title = (
|
||||
content_text[: min(cut_point_indexes)]
|
||||
if len(cut_point_indexes) > 0
|
||||
else content_text
|
||||
)
|
||||
return title
|
||||
|
||||
|
||||
def check_password_complexity(password):
|
||||
lowercase_pattern = r"[a-z]"
|
||||
uppercase_pattern = r"[A-Z]"
|
||||
digit_pattern = r"\d"
|
||||
special_pattern = r'[!@#$%^&*(),.?":{}|<>]'
|
||||
|
||||
password_lowercase_one = bool(re.search(lowercase_pattern, password))
|
||||
password_uppercase_one = bool(re.search(uppercase_pattern, password))
|
||||
password_digit_one = bool(re.search(digit_pattern, password))
|
||||
password_special_one = bool(re.search(special_pattern, password))
|
||||
|
||||
if (
|
||||
password_lowercase_one
|
||||
and password_uppercase_one
|
||||
and password_digit_one
|
||||
and password_special_one
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -10,11 +10,11 @@ CODEBASE_ROOT=$GIT_REPO_ROOT/$APP_PARENT_FOLDER/$APP_NAME
|
||||
SITE_DEPLOY_FOLDER=$GIT_REPO_ROOT/sites/$APP_NAME/deploy
|
||||
|
||||
|
||||
echo APP_NAME=$APP_NAME > .env
|
||||
echo export APP_NAME=$APP_NAME > .env
|
||||
cat $SITE_DEPLOY_FOLDER/common/.env >> .env
|
||||
echo GIT_REPO_ROOT=$(git rev-parse --show-toplevel) >> .env
|
||||
echo CODEBASE_ROOT=$GIT_REPO_ROOT/$APP_PARENT_FOLDER/$APP_NAME >> .env
|
||||
echo SITE_DEPLOY_FOLDER=$GIT_REPO_ROOT/sites/$APP_NAME/deploy >> .env
|
||||
echo export GIT_REPO_ROOT=$(git rev-parse --show-toplevel) >> .env
|
||||
echo export CODEBASE_ROOT=$GIT_REPO_ROOT/$APP_PARENT_FOLDER/$APP_NAME >> .env
|
||||
echo export SITE_DEPLOY_FOLDER=$GIT_REPO_ROOT/sites/$APP_NAME/deploy >> .env
|
||||
cat $SITE_DEPLOY_FOLDER/common/.host.env >> .env
|
||||
cat $SITE_DEPLOY_FOLDER/local/.env >> .env
|
||||
|
||||
|
||||
@ -2,13 +2,13 @@ import logging
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
from app.notification.webapi.providers import common
|
||||
from app.notification.webapi.providers import logger
|
||||
from app.notification.webapi.providers import router
|
||||
from app.notification.webapi.providers import database
|
||||
from app.notification.webapi.providers import scheduler
|
||||
from app.notification.webapi.providers import message_queue
|
||||
from app.notification.webapi.providers import exception_handler
|
||||
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 message_queue
|
||||
from webapi.providers import exception_handler
|
||||
from .freeleaps_app import FreeleapsApp
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
from app.notification.backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from app.notification.backend.models.constants import NotificationChannel
|
||||
from app.notification.webapi.utils.email_consumer import EmailMQConsumer
|
||||
from app.notification.webapi.utils.sms_consumer import SmsMQConsumer
|
||||
from backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from backend.models.constants import NotificationChannel
|
||||
from webapi.utils.email_consumer import EmailMQConsumer
|
||||
from webapi.utils.sms_consumer import SmsMQConsumer
|
||||
|
||||
|
||||
class FreeleapsApp(FastAPI):
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from app.notification.webapi.bootstrap.application import create_app
|
||||
from app.notification.webapi.config.site_settings import site_settings
|
||||
from webapi.bootstrap.application import create_app
|
||||
from webapi.config.site_settings import site_settings
|
||||
from fastapi.responses import RedirectResponse
|
||||
import uvicorn
|
||||
from typing import Any
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.notification.webapi.config.site_settings import site_settings
|
||||
from webapi.config.site_settings import site_settings
|
||||
|
||||
|
||||
def register(app):
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from app.notification.webapi.config.site_settings import site_settings
|
||||
from webapi.config.site_settings import site_settings
|
||||
from beanie import init_beanie
|
||||
from motor.motor_asyncio import AsyncIOMotorClient
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import sys
|
||||
from loguru import logger
|
||||
from infra.config.log_settings import log_settings
|
||||
from common.config.log_settings import log_settings
|
||||
|
||||
|
||||
def register(app=None):
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from app.notification.webapi.routes import api_router
|
||||
from webapi.routes import api_router
|
||||
|
||||
from starlette import routing
|
||||
|
||||
|
||||
@ -8,9 +8,9 @@ from fastapi import (
|
||||
WebSocketDisconnect,
|
||||
)
|
||||
from starlette.websockets import WebSocketState
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from app.notification.backend.business.notification_manager import NotificationManager
|
||||
from common.log.module_logger import ModuleLogger
|
||||
from common.config.app_settings import app_settings
|
||||
from backend.business.notification_manager import NotificationManager
|
||||
|
||||
|
||||
async def consume_message(requester_key, message, args):
|
||||
|
||||
@ -2,9 +2,9 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
import traceback
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
|
||||
from app.notification.backend.application.notification_hub import NotificationHub
|
||||
from backend.application.notification_hub import NotificationHub
|
||||
from pydantic import BaseModel
|
||||
from app.notification.backend.models.constants import NotificationChannel
|
||||
from backend.models.constants import NotificationChannel
|
||||
from typing import Dict
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from app.notification.backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from app.notification.backend.infra.email_handler import EmailHandler
|
||||
from common.config.app_settings import app_settings
|
||||
from backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from backend.infra.email_handler import EmailHandler
|
||||
|
||||
|
||||
class EmailMQConsumer:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from app.notification.backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from app.notification.backend.infra.sms_handler import SmsHandler
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from common.config.app_settings import app_settings
|
||||
from backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from backend.infra.sms_handler import SmsHandler
|
||||
from common.log.module_logger import ModuleLogger
|
||||
|
||||
|
||||
class SmsMQConsumer:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export MONGODB_URI='mongodb+srv://jetli:8IHKx6dZK8BfugGp@freeleaps2.hanbj.mongodb.net/'
|
||||
export RABBITMQ_HOST=if030-w2-if-vm.mathmast.com
|
||||
export RABBITMQ_PORT=5672
|
||||
export FREELEAPS_ENV=alpha
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export SERVICE_API_ACCESS_HOST=0.0.0.0
|
||||
export SERVICE_API_ACCESS_PORT=8004
|
||||
export SERVICE_API_ACCESS_PORT=8003
|
||||
export CONTAINER_APP_ROOT=/app
|
||||
export LOG_BASE_PATH=$CONTAINER_APP_ROOT/log/$APP_NAME
|
||||
export BACKEND_LOG_FILE_NAME=$APP_NAME
|
||||
@ -8,3 +8,7 @@ export SENDGRID_API_KEY='SG.jAZatAvjQiCAfIwmIu36JA.8NWnGfNcVNkDfwFqGMX-S_DsiOsqU
|
||||
export EMAIL_FROM=freeleaps@freeleaps.com
|
||||
export TWILIO_ACCOUNT_SID=ACf8c9283a6acda060258eadb29be58bc8
|
||||
export TWILIO_AUTH_TOKEN=ef160748cc22c8b7195b49df4b8eca7e
|
||||
export SYSTEM_USER_ID=117f191e810c19729de860aa
|
||||
export SMS_FROM=+16898887156
|
||||
export EMAIL_FROM=freeleaps@freeleaps.com
|
||||
export SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0
|
||||
|
||||
@ -9,15 +9,20 @@ services:
|
||||
restart: always
|
||||
environment:
|
||||
- APP_NAME=${APP_NAME}
|
||||
- MONGODB_NAME=${MONGODB_NAME}
|
||||
- MONGODB_PORT=${MONGODB_PORT}
|
||||
- MONGODB_URI=${MONGODB_URI}
|
||||
- SERVICE_API_ACCESS_HOST=${SERVICE_API_ACCESS_HOST}
|
||||
- SERVICE_API_ACCESS_PORT=${SERVICE_API_ACCESS_PORT}
|
||||
- RABBITMQ_HOST=${RABBITMQ_HOST}
|
||||
- RABBITMQ_PORT=${RABBITMQ_PORT}
|
||||
- SYSTEM_USER_ID=${SYSTEM_USER_ID}
|
||||
- SMS_FROM=${SMS_FROM}
|
||||
- EMAIL_FROM=${EMAIL_FROM}
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- SENDGRID_API_KEY=${SENDGRID_API_KEY}
|
||||
- TWILIO_ACCOUNT_SID=${TWILIO_ACCOUNT_SID}
|
||||
- TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN}
|
||||
- LOG_BASE_PATH=${LOG_BASE_PATH}
|
||||
- BACKEND_LOG_FILE_NAME=${BACKEND_LOG_FILE_NAME}
|
||||
- APPLICATION_ACTIVITY_LOG=${APPLICATION_ACTIVITY_LOG}
|
||||
- JWT_SECRET_KEY=${JWT_SECRET_KEY}
|
||||
ports:
|
||||
- ${SERVICE_API_ACCESS_PORT}:${SERVICE_API_ACCESS_PORT}
|
||||
command:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export MONGODB_URI=mongodb://freeleaps2-mongodb:27017/
|
||||
export RABBITMQ_HOST=freeleaps2-rabbitmq
|
||||
export RABBITMQ_PORT=5672
|
||||
export FREELEAPS_ENV=dev
|
||||
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
export MONGODB_URI=mongodb://localhost:27017/
|
||||
export RABBITMQ_HOST=localhost
|
||||
export RABBITMQ_PORT=5672
|
||||
export FREELEAPS_ENV=local
|
||||
export LOG_BASE_PATH=${CODEBASE_ROOT}/log
|
||||
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export MONGODB_URI='mongodb+srv://freeadmin:0eMV0bt8oyaknA0m@freeleaps2.zmsmpos.mongodb.net/?retryWrites=true&w=majority'
|
||||
export RABBITMQ_HOST=if010-w2-if-vm.mathmast.com
|
||||
export RABBITMQ_PORT=5672
|
||||
export FREELEAPS_ENV=prod
|
||||
|
||||
Loading…
Reference in New Issue
Block a user