diff --git a/apps/authentication/common/config/app_settings.py b/apps/authentication/common/config/app_settings.py index 2f7e92f..809d1b4 100644 --- a/apps/authentication/common/config/app_settings.py +++ b/apps/authentication/common/config/app_settings.py @@ -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" diff --git a/apps/central_storage/common/config/app_settings.py b/apps/central_storage/common/config/app_settings.py index 95889f7..d9ba0ed 100644 --- a/apps/central_storage/common/config/app_settings.py +++ b/apps/central_storage/common/config/app_settings.py @@ -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" diff --git a/apps/content/common/config/app_settings.py b/apps/content/common/config/app_settings.py index d68dd8f..b05d89d 100644 --- a/apps/content/common/config/app_settings.py +++ b/apps/content/common/config/app_settings.py @@ -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" diff --git a/apps/notification/backend/application/notification_hub.py b/apps/notification/backend/application/notification_hub.py index 1283240..3af0b4b 100644 --- a/apps/notification/backend/application/notification_hub.py +++ b/apps/notification/backend/application/notification_hub.py @@ -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: diff --git a/apps/notification/backend/business/notification_manager.py b/apps/notification/backend/business/notification_manager.py index b86e11a..db5274a 100644 --- a/apps/notification/backend/business/notification_manager.py +++ b/apps/notification/backend/business/notification_manager.py @@ -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 diff --git a/apps/notification/backend/infra/email_handler.py b/apps/notification/backend/infra/email_handler.py index 8dd84fb..03908b3 100644 --- a/apps/notification/backend/infra/email_handler.py +++ b/apps/notification/backend/infra/email_handler.py @@ -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: diff --git a/apps/notification/backend/infra/rabbitmq/async_client.py b/apps/notification/backend/infra/rabbitmq/async_client.py index b0b6665..2e4ce72 100644 --- a/apps/notification/backend/infra/rabbitmq/async_client.py +++ b/apps/notification/backend/infra/rabbitmq/async_client.py @@ -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() diff --git a/apps/notification/backend/infra/rabbitmq/async_publisher.py b/apps/notification/backend/infra/rabbitmq/async_publisher.py index 594753d..77fb2cf 100644 --- a/apps/notification/backend/infra/rabbitmq/async_publisher.py +++ b/apps/notification/backend/infra/rabbitmq/async_publisher.py @@ -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 diff --git a/apps/notification/backend/infra/rabbitmq/async_subscriber.py b/apps/notification/backend/infra/rabbitmq/async_subscriber.py index 07a3ef6..0820d31 100644 --- a/apps/notification/backend/infra/rabbitmq/async_subscriber.py +++ b/apps/notification/backend/infra/rabbitmq/async_subscriber.py @@ -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 diff --git a/apps/notification/backend/infra/sms_handler.py b/apps/notification/backend/infra/sms_handler.py index 429e858..b6f784e 100644 --- a/apps/notification/backend/infra/sms_handler.py +++ b/apps/notification/backend/infra/sms_handler.py @@ -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 diff --git a/apps/notification/backend/models/constants.py b/apps/notification/backend/models/constants.py index 205a139..d8ff9ad 100644 --- a/apps/notification/backend/models/constants.py +++ b/apps/notification/backend/models/constants.py @@ -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): diff --git a/apps/notification/backend/services/notification_publisher_service.py b/apps/notification/backend/services/notification_publisher_service.py index da2159e..d1fcfff 100644 --- a/apps/notification/backend/services/notification_publisher_service.py +++ b/apps/notification/backend/services/notification_publisher_service.py @@ -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: diff --git a/apps/notification/common/config/app_settings.py b/apps/notification/common/config/app_settings.py index be3ebdd..620d05c 100644 --- a/apps/notification/common/config/app_settings.py +++ b/apps/notification/common/config/app_settings.py @@ -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" diff --git a/apps/notification/common/constants/__init__.py b/apps/notification/common/constants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notification/common/constants/contract.py b/apps/notification/common/constants/contract.py new file mode 100644 index 0000000..8d1ad3f --- /dev/null +++ b/apps/notification/common/constants/contract.py @@ -0,0 +1,8 @@ +from enum import IntEnum + + +class UserContractRole(IntEnum): + VISITER = 0x1 + REQUESTER = 0x2 + PROVIDER = 0x4 + diff --git a/apps/notification/common/constants/payment.py b/apps/notification/common/constants/payment.py new file mode 100644 index 0000000..65c76a7 --- /dev/null +++ b/apps/notification/common/constants/payment.py @@ -0,0 +1,5 @@ +from enum import IntEnum + +class PaymentPlanType(IntEnum): + STAGED = 0 + diff --git a/apps/notification/common/constants/region.py b/apps/notification/common/constants/region.py new file mode 100644 index 0000000..ac87f72 --- /dev/null +++ b/apps/notification/common/constants/region.py @@ -0,0 +1,5 @@ +from enum import IntEnum + +class UserRegion(IntEnum): + OTHER = 0 + ZH_CN = 1 diff --git a/apps/notification/common/exception/__init__.py b/apps/notification/common/exception/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notification/common/exception/exceptions.py b/apps/notification/common/exception/exceptions.py new file mode 100644 index 0000000..2c489c3 --- /dev/null +++ b/apps/notification/common/exception/exceptions.py @@ -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 diff --git a/apps/notification/common/log/__init__.py b/apps/notification/common/log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notification/common/log/application_logger.py b/apps/notification/common/log/application_logger.py new file mode 100644 index 0000000..c1222bb --- /dev/null +++ b/apps/notification/common/log/application_logger.py @@ -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, + ) diff --git a/apps/notification/common/log/base_logger.py b/apps/notification/common/log/base_logger.py new file mode 100644 index 0000000..f35aaef --- /dev/null +++ b/apps/notification/common/log/base_logger.py @@ -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) diff --git a/apps/notification/common/log/business_metric_logger.py b/apps/notification/common/log/business_metric_logger.py new file mode 100644 index 0000000..95383ab --- /dev/null +++ b/apps/notification/common/log/business_metric_logger.py @@ -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" + ) diff --git a/apps/notification/common/log/function_logger.py b/apps/notification/common/log/function_logger.py new file mode 100644 index 0000000..4388a5e --- /dev/null +++ b/apps/notification/common/log/function_logger.py @@ -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 + }, + ) diff --git a/apps/notification/common/log/log_utils.py b/apps/notification/common/log/log_utils.py new file mode 100644 index 0000000..579dee8 --- /dev/null +++ b/apps/notification/common/log/log_utils.py @@ -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 diff --git a/apps/notification/common/log/module_logger.py b/apps/notification/common/log/module_logger.py new file mode 100644 index 0000000..3426b0b --- /dev/null +++ b/apps/notification/common/log/module_logger.py @@ -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, + ) diff --git a/apps/notification/common/log/user_logger.py b/apps/notification/common/log/user_logger.py new file mode 100644 index 0000000..d931975 --- /dev/null +++ b/apps/notification/common/log/user_logger.py @@ -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 + ) diff --git a/apps/notification/common/token/token_manager.py b/apps/notification/common/token/token_manager.py new file mode 100644 index 0000000..3034e1f --- /dev/null +++ b/apps/notification/common/token/token_manager.py @@ -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" + ) diff --git a/apps/notification/common/utils/date.py b/apps/notification/common/utils/date.py new file mode 100644 index 0000000..79451d1 --- /dev/null +++ b/apps/notification/common/utils/date.py @@ -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 diff --git a/apps/notification/common/utils/region.py b/apps/notification/common/utils/region.py new file mode 100644 index 0000000..4212e02 --- /dev/null +++ b/apps/notification/common/utils/region.py @@ -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 \ No newline at end of file diff --git a/apps/notification/common/utils/string.py b/apps/notification/common/utils/string.py new file mode 100644 index 0000000..359c6b4 --- /dev/null +++ b/apps/notification/common/utils/string.py @@ -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 diff --git a/apps/notification/start_fastapi.sh b/apps/notification/start_fastapi.sh index cafe5b4..b21772f 100755 --- a/apps/notification/start_fastapi.sh +++ b/apps/notification/start_fastapi.sh @@ -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 diff --git a/apps/notification/webapi/bootstrap/application.py b/apps/notification/webapi/bootstrap/application.py index e01d540..d2355fd 100644 --- a/apps/notification/webapi/bootstrap/application.py +++ b/apps/notification/webapi/bootstrap/application.py @@ -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 diff --git a/apps/notification/webapi/bootstrap/freeleaps_app.py b/apps/notification/webapi/bootstrap/freeleaps_app.py index d1db11c..dca6d6e 100644 --- a/apps/notification/webapi/bootstrap/freeleaps_app.py +++ b/apps/notification/webapi/bootstrap/freeleaps_app.py @@ -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): diff --git a/apps/notification/webapi/main.py b/apps/notification/webapi/main.py index abda1ed..d325986 100755 --- a/apps/notification/webapi/main.py +++ b/apps/notification/webapi/main.py @@ -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 diff --git a/apps/notification/webapi/providers/common.py b/apps/notification/webapi/providers/common.py index ae41211..1dd849f 100644 --- a/apps/notification/webapi/providers/common.py +++ b/apps/notification/webapi/providers/common.py @@ -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): diff --git a/apps/notification/webapi/providers/database.py b/apps/notification/webapi/providers/database.py index 0b378a5..ccc3992 100644 --- a/apps/notification/webapi/providers/database.py +++ b/apps/notification/webapi/providers/database.py @@ -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 diff --git a/apps/notification/webapi/providers/logger.py b/apps/notification/webapi/providers/logger.py index 0b37c27..9bc8adb 100644 --- a/apps/notification/webapi/providers/logger.py +++ b/apps/notification/webapi/providers/logger.py @@ -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): diff --git a/apps/notification/webapi/providers/router.py b/apps/notification/webapi/providers/router.py index 806b0d0..3ad11ae 100644 --- a/apps/notification/webapi/providers/router.py +++ b/apps/notification/webapi/providers/router.py @@ -1,4 +1,4 @@ -from app.notification.webapi.routes import api_router +from webapi.routes import api_router from starlette import routing diff --git a/apps/notification/webapi/routes/online_platform_notification.py b/apps/notification/webapi/routes/online_platform_notification.py index 54b0c89..4d83d5c 100644 --- a/apps/notification/webapi/routes/online_platform_notification.py +++ b/apps/notification/webapi/routes/online_platform_notification.py @@ -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): diff --git a/apps/notification/webapi/routes/send_notification.py b/apps/notification/webapi/routes/send_notification.py index 6067728..1bf6374 100644 --- a/apps/notification/webapi/routes/send_notification.py +++ b/apps/notification/webapi/routes/send_notification.py @@ -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() diff --git a/apps/notification/webapi/utils/email_consumer.py b/apps/notification/webapi/utils/email_consumer.py index 60e7cc5..0473ddc 100644 --- a/apps/notification/webapi/utils/email_consumer.py +++ b/apps/notification/webapi/utils/email_consumer.py @@ -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: diff --git a/apps/notification/webapi/utils/sms_consumer.py b/apps/notification/webapi/utils/sms_consumer.py index 077e4a0..2045a55 100644 --- a/apps/notification/webapi/utils/sms_consumer.py +++ b/apps/notification/webapi/utils/sms_consumer.py @@ -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: diff --git a/sites/notification/deploy/alpha/.env b/sites/notification/deploy/alpha/.env index 68beb14..5a2a30b 100755 --- a/sites/notification/deploy/alpha/.env +++ b/sites/notification/deploy/alpha/.env @@ -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 diff --git a/sites/notification/deploy/common/.env b/sites/notification/deploy/common/.env index 86017aa..6678b1b 100755 --- a/sites/notification/deploy/common/.env +++ b/sites/notification/deploy/common/.env @@ -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 diff --git a/sites/notification/deploy/common/docker-compose.yaml b/sites/notification/deploy/common/docker-compose.yaml index e407d61..83d7ac7 100755 --- a/sites/notification/deploy/common/docker-compose.yaml +++ b/sites/notification/deploy/common/docker-compose.yaml @@ -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: diff --git a/sites/notification/deploy/dev/.env b/sites/notification/deploy/dev/.env index a43a6fe..683d0a8 100755 --- a/sites/notification/deploy/dev/.env +++ b/sites/notification/deploy/dev/.env @@ -1,3 +1,4 @@ -export MONGODB_URI=mongodb://freeleaps2-mongodb:27017/ +export RABBITMQ_HOST=freeleaps2-rabbitmq +export RABBITMQ_PORT=5672 export FREELEAPS_ENV=dev diff --git a/sites/notification/deploy/local/.env b/sites/notification/deploy/local/.env index 4938c5e..f271362 100644 --- a/sites/notification/deploy/local/.env +++ b/sites/notification/deploy/local/.env @@ -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 + diff --git a/sites/notification/deploy/prod/.env b/sites/notification/deploy/prod/.env index 0167e99..f5bb7b7 100755 --- a/sites/notification/deploy/prod/.env +++ b/sites/notification/deploy/prod/.env @@ -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