diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e21a1e1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +## [1.1.1](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/compare/v1.1.0...v1.1.1) (2025-03-17) + + +### Bug Fixes + +* invalid import path for app config ([4dfbab4](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/commit/4dfbab4d4de83fbe5140c05071d8138cb09ef688)) + +# [1.1.0](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/compare/v1.0.0...v1.1.0) (2025-03-17) + + +### Features + +* **observability:** add configuration options and implement metrics/probes APIs ([d5e42d3](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/commit/d5e42d31a4ce1ac64f6c5dcf5688c0acae1fdaa6)) +* **probes:** add metrics and probes APIs for application health checks ([9754576](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/commit/9754576d28066c9805e5e4673e03fd79b3a603bd)) + +# 1.0.0 (2025-03-06) + + +### Bug Fixes + +* **rabbitmq:** correct syntax for port and virtual host parameters in AsyncMQClient ([78c7217](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/commit/78c72179ec3fdb15d4af01bee15a441f0e383638)) + + +### Features + +* **notification:** add rabbitmq credentials relates notification services configs ([853d817](https://dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub/commit/853d81793332513e89286d61429444d520252c27)) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..524cb55 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.1 diff --git a/apps/content/backend/document/document_manager.py b/apps/content/backend/document/document_manager.py index 1d62ebc..8db5f40 100644 --- a/apps/content/backend/document/document_manager.py +++ b/apps/content/backend/document/document_manager.py @@ -1,6 +1,7 @@ from common.config.app_settings import app_settings from backend.content.models import DocumentDoc from backend.document.models import BasicProfileDoc +from datetime import datetime, timezone import httpx import base64 @@ -53,9 +54,12 @@ class DocumentManager: print(f"Failed to queue deletion: {response.text}") async def cleanup_document(self): + # Get today's date at midnight (UTC) + today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) # Corrected query with regex documents = await DocumentDoc.find( - {"created_by": {"$regex": "^content-service-"}} + {"created_by": {"$regex": "^content-service-"}, + "create_time": {"$lt": today_start}} ).to_list() if documents: diff --git a/apps/content/scheduler/scheduler_manager.py b/apps/content/scheduler/scheduler_manager.py index d35fe86..fe1bb79 100755 --- a/apps/content/scheduler/scheduler_manager.py +++ b/apps/content/scheduler/scheduler_manager.py @@ -1,5 +1,6 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.date import DateTrigger +from apscheduler.triggers.cron import CronTrigger from datetime import datetime, timedelta from scheduler.refresh_sharepoint_content_job import ( refresh_sharepoint_content_job, @@ -28,15 +29,11 @@ async def register_job(scheduler: AsyncIOScheduler): "date", run_date=datetime(2025, 2, 7, 20, 0, 0), ) - # Register cleanup_document_job as a one-time job - # This job is just one-time job for removing many unused documents - # Run already, now comment it out - # await init_lock(ScheduleJobLocker.CLEANUP_DOCUMENT_JOB_LOCKER) - # execution_time = datetime.now() + timedelta( - # seconds=60 - # ) # Schedule to run 60 seconds from now - # scheduler.add_job( - # cleanup_document_job, # Job function - # trigger=DateTrigger(run_date=execution_time), # One-time trigger - # id="cleanup_document_one_time", # Optional: Give the job an ID - # ) + + await init_lock(ScheduleJobLocker.CLEANUP_DOCUMENT_JOB_LOCKER) + scheduler.add_job( + cleanup_document_job, + trigger=CronTrigger(hour=2, minute=0), # Runs every day at 2:00 AM + id="cleanup_document_daily", + ) + diff --git a/apps/notification/backend/business/notification_manager.py b/apps/notification/backend/business/notification_manager.py index 786338f..eac6f63 100644 --- a/apps/notification/backend/business/notification_manager.py +++ b/apps/notification/backend/business/notification_manager.py @@ -86,26 +86,44 @@ class NotificationManager: properties: dict, region: Optional[UserRegion] = None, ) -> str: - # leverage the information in properties to enrich the message. - message_subject = None - message = None - if subject.lower() == "payment": - pass - - # Default region to be international if not set + # Default region to international if not set if region is None: region = UserRegion.OTHER - message_subject = SystemNotifications[region][subject.lower()][event.lower()][ - "message_subject" - ] - message = SystemNotifications[region][subject.lower()][event.lower()]["message"] + subject_lower = subject.lower() + event_lower = event.lower() - if event.lower() == "authentication": - message = message.format(properties["auth_code"]) - if not message: - raise RuntimeError("unsupported event:{}".format(event)) - return message, message_subject + try: + # Get message template and subject from SystemNotifications + notification_config = SystemNotifications[region][subject_lower][event_lower] + message = notification_config["message"] + message_subject = notification_config["message_subject"] + + # Handle authentication specific formatting + if event_lower == "authentication" and "auth_code" in properties: + message = message.format(properties["auth_code"]) + + # Append content_text if it exists in properties + if properties.get("content_text"): + if isinstance(properties["content_text"], dict): + # If content_text is a dictionary, use format with kwargs + message = message.format(**properties["content_text"]) + elif isinstance(properties["content_text"], str): + # If content_text is a string, append it with proper spacing + content = properties["content_text"].strip() + if message and content: + # Use HTML line breaks for email compatibility + message = f"{message.rstrip()}

{content}" + else: + # If either is empty, just use the non-empty one + message = message or content + + return message, message_subject + + except KeyError as e: + raise RuntimeError(f"Unsupported configuration - subject: {subject_lower}, event: {event_lower}, error: {str(e)}") + except ValueError as e: + raise RuntimeError(f"Invalid message format - error: {str(e)}") async def send_in_app_notification( self, receiver_id: str, subject: str, event: str, properties: dict = None diff --git a/apps/notification/webapi/bootstrap/application.py b/apps/notification/webapi/bootstrap/application.py index 98ebf6f..d98218d 100644 --- a/apps/notification/webapi/bootstrap/application.py +++ b/apps/notification/webapi/bootstrap/application.py @@ -14,6 +14,7 @@ from webapi.providers import metrics from .freeleaps_app import FreeleapsApp from common.config.app_settings import app_settings +from prometheus_fastapi_instrumentator import Instrumentator def create_app() -> FastAPI: logging.info("App initializing") @@ -38,6 +39,8 @@ def create_app() -> FastAPI: # Call the custom_openapi function to change the OpenAPI version customize_openapi_security(app) + # expose prometheus metrics + Instrumentator().instrument(app).expose(app) return app diff --git a/apps/payment/backend/business/stripe_manager.py b/apps/payment/backend/business/stripe_manager.py index 49fef8a..656f884 100644 --- a/apps/payment/backend/business/stripe_manager.py +++ b/apps/payment/backend/business/stripe_manager.py @@ -25,6 +25,14 @@ class StripeManager: async def create_account_link(self, account_id: str, link_type: str = "account_onboarding") -> Optional[str]: account = stripe.Account.retrieve(account_id) # For account_update, try to show dashboard if TOS is accepted + + self.module_logger.log_info("create_account_link urls", + { + "redirect_url": "{}/work".format(self.site_url_root), + "refresh_url": "{}/front-door".format(self.site_url_root), + "return_url": "{}/work".format(self.site_url_root) + } + ) if link_type == "account_update" and account.tos_acceptance.date: login_link = stripe.Account.create_login_link( account_id, @@ -284,10 +292,10 @@ class StripeManager: }, }, mode="payment", - success_url="{}/work-space".format( + success_url="{}/projects".format( self.site_url_root ), # needs to be set, local: http://localhost/ - cancel_url="{}/work-space".format(self.site_url_root), + cancel_url="{}/projects".format(self.site_url_root), ) if session: