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: