pipeline issues fix
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-09-25 11:59:54 +09:00
parent dc50a9858e
commit 4e3768a6ee
39 changed files with 1297 additions and 739 deletions

View File

@@ -0,0 +1 @@
# Notification Service Package

View File

@@ -1,17 +1,19 @@
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from shared.config import settings
from shared.database import get_db
from services.user_service.main import get_current_user
from services.user_service.models import User
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from datetime import datetime
import httpx
import asyncio
import json
from datetime import datetime
from typing import Any, Dict, List, Optional
import httpx
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from services.user_service.main import get_current_user
from services.user_service.models import User
from shared.config import settings
from shared.database import get_db
app = FastAPI(title="Notification Service", version="1.0.0")
@@ -56,42 +58,43 @@ class FCMClient:
def __init__(self, server_key: str):
self.server_key = server_key
self.fcm_url = "https://fcm.googleapis.com/fcm/send"
async def send_notification(self, tokens: List[str], notification_data: dict) -> dict:
async def send_notification(
self, tokens: List[str], notification_data: dict
) -> dict:
"""Send push notification via FCM"""
if not self.server_key:
print("FCM Server Key not configured - notification would be sent")
return {"success_count": len(tokens), "failure_count": 0}
headers = {
"Authorization": f"key={self.server_key}",
"Content-Type": "application/json"
"Content-Type": "application/json",
}
payload = {
"registration_ids": tokens,
"notification": {
"title": notification_data.get("title"),
"body": notification_data.get("body"),
"sound": "default"
"sound": "default",
},
"data": notification_data.get("data", {}),
"priority": "high" if notification_data.get("priority") == "high" else "normal"
"priority": "high"
if notification_data.get("priority") == "high"
else "normal",
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(
self.fcm_url,
headers=headers,
json=payload,
timeout=10.0
self.fcm_url, headers=headers, json=payload, timeout=10.0
)
result = response.json()
return {
"success_count": result.get("success", 0),
"failure_count": result.get("failure", 0),
"results": result.get("results", [])
"results": result.get("results", []),
}
except Exception as e:
print(f"FCM Error: {e}")
@@ -107,30 +110,29 @@ notification_stats = {
"total_sent": 0,
"successful_deliveries": 0,
"failed_deliveries": 0,
"emergency_notifications": 0
"emergency_notifications": 0,
}
@app.post("/api/v1/register-device")
async def register_device_token(
device_data: DeviceToken,
current_user: User = Depends(get_current_user)
device_data: DeviceToken, current_user: User = Depends(get_current_user)
):
"""Register device token for push notifications"""
if current_user.id not in user_device_tokens:
user_device_tokens[current_user.id] = []
# Remove existing token if present
if device_data.token in user_device_tokens[current_user.id]:
user_device_tokens[current_user.id].remove(device_data.token)
# Add new token
user_device_tokens[current_user.id].append(device_data.token)
# Keep only last 3 tokens per user
user_device_tokens[current_user.id] = user_device_tokens[current_user.id][-3:]
return {"message": "Device token registered successfully"}
@@ -140,25 +142,27 @@ async def send_notification(
target_user_id: int,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Send notification to specific user"""
# Check if target user exists and accepts notifications
result = await db.execute(select(User).filter(User.id == target_user_id))
target_user = result.scalars().first()
if not target_user:
raise HTTPException(status_code=404, detail="Target user not found")
if not target_user.push_notifications_enabled:
raise HTTPException(status_code=403, detail="User has disabled push notifications")
raise HTTPException(
status_code=403, detail="User has disabled push notifications"
)
# Get user's device tokens
tokens = user_device_tokens.get(target_user_id, [])
if not tokens:
raise HTTPException(status_code=400, detail="No device tokens found for user")
# Send notification in background
background_tasks.add_task(
send_push_notification,
@@ -167,10 +171,10 @@ async def send_notification(
"title": notification.title,
"body": notification.body,
"data": notification.data or {},
"priority": notification.priority
}
"priority": notification.priority,
},
)
return {"message": "Notification queued for delivery"}
@@ -178,57 +182,57 @@ async def send_notification(
async def send_emergency_notifications(
emergency_data: EmergencyNotificationRequest,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Send emergency notifications to nearby users"""
if not emergency_data.user_ids:
return {"message": "No users to notify"}
# Get users who have emergency notifications enabled
result = await db.execute(
select(User).filter(
User.id.in_(emergency_data.user_ids),
User.emergency_notifications_enabled == True,
User.is_active == True
User.is_active == True,
)
)
users = result.scalars().all()
# Collect all device tokens
all_tokens = []
for user in users:
tokens = user_device_tokens.get(user.id, [])
all_tokens.extend(tokens)
if not all_tokens:
return {"message": "No device tokens found for target users"}
# Prepare emergency notification
emergency_title = "🚨 Emergency Alert Nearby"
emergency_body = f"Someone needs help in your area. Alert type: {emergency_data.alert_type}"
emergency_body = (
f"Someone needs help in your area. Alert type: {emergency_data.alert_type}"
)
if emergency_data.location:
emergency_body += f" Location: {emergency_data.location}"
notification_data = {
"title": emergency_title,
"body": emergency_body,
"data": {
"type": "emergency",
"alert_id": str(emergency_data.alert_id),
"alert_type": emergency_data.alert_type
"alert_type": emergency_data.alert_type,
},
"priority": "high"
"priority": "high",
}
# Send notifications in background
background_tasks.add_task(
send_emergency_push_notification,
all_tokens,
notification_data
send_emergency_push_notification, all_tokens, notification_data
)
return {"message": f"Emergency notifications queued for {len(users)} users"}
@@ -236,14 +240,16 @@ async def send_push_notification(tokens: List[str], notification_data: dict):
"""Send push notification using FCM"""
try:
result = await fcm_client.send_notification(tokens, notification_data)
# Update stats
notification_stats["total_sent"] += len(tokens)
notification_stats["successful_deliveries"] += result["success_count"]
notification_stats["failed_deliveries"] += result["failure_count"]
print(f"Notification sent: {result['success_count']} successful, {result['failure_count']} failed")
print(
f"Notification sent: {result['success_count']} successful, {result['failure_count']} failed"
)
except Exception as e:
print(f"Failed to send notification: {e}")
notification_stats["failed_deliveries"] += len(tokens)
@@ -254,15 +260,17 @@ async def send_emergency_push_notification(tokens: List[str], notification_data:
try:
# Emergency notifications are sent immediately with high priority
result = await fcm_client.send_notification(tokens, notification_data)
# Update stats
notification_stats["total_sent"] += len(tokens)
notification_stats["successful_deliveries"] += result["success_count"]
notification_stats["failed_deliveries"] += result["failure_count"]
notification_stats["emergency_notifications"] += len(tokens)
print(f"Emergency notification sent: {result['success_count']} successful, {result['failure_count']} failed")
print(
f"Emergency notification sent: {result['success_count']} successful, {result['failure_count']} failed"
)
except Exception as e:
print(f"Failed to send emergency notification: {e}")
notification_stats["emergency_notifications"] += len(tokens)
@@ -275,20 +283,20 @@ async def send_calendar_reminder(
message: str,
user_ids: List[int],
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Send calendar reminder notifications"""
# Get users who have notifications enabled
result = await db.execute(
select(User).filter(
User.id.in_(user_ids),
User.push_notifications_enabled == True,
User.is_active == True
User.is_active == True,
)
)
users = result.scalars().all()
# Send notifications to each user
for user in users:
tokens = user_device_tokens.get(user.id, [])
@@ -300,49 +308,43 @@ async def send_calendar_reminder(
"title": title,
"body": message,
"data": {"type": "calendar_reminder"},
"priority": "normal"
}
"priority": "normal",
},
)
return {"message": f"Calendar reminders queued for {len(users)} users"}
@app.delete("/api/v1/device-token")
async def unregister_device_token(
token: str,
current_user: User = Depends(get_current_user)
token: str, current_user: User = Depends(get_current_user)
):
"""Unregister device token"""
if current_user.id in user_device_tokens:
tokens = user_device_tokens[current_user.id]
if token in tokens:
tokens.remove(token)
if not tokens:
del user_device_tokens[current_user.id]
return {"message": "Device token unregistered successfully"}
@app.get("/api/v1/my-devices")
async def get_my_device_tokens(
current_user: User = Depends(get_current_user)
):
async def get_my_device_tokens(current_user: User = Depends(get_current_user)):
"""Get user's registered device tokens (masked for security)"""
tokens = user_device_tokens.get(current_user.id, [])
masked_tokens = [f"{token[:8]}...{token[-8:]}" for token in tokens]
return {
"device_count": len(tokens),
"tokens": masked_tokens
}
return {"device_count": len(tokens), "tokens": masked_tokens}
@app.get("/api/v1/stats", response_model=NotificationStats)
async def get_notification_stats(current_user: User = Depends(get_current_user)):
"""Get notification service statistics"""
return NotificationStats(**notification_stats)
@@ -350,12 +352,13 @@ async def get_notification_stats(current_user: User = Depends(get_current_user))
async def health_check():
"""Health check endpoint"""
return {
"status": "healthy",
"status": "healthy",
"service": "notification-service",
"fcm_configured": bool(settings.FCM_SERVER_KEY)
"fcm_configured": bool(settings.FCM_SERVER_KEY),
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8005)
uvicorn.run(app, host="0.0.0.0", port=8005)