diff --git a/.gitignore b/.gitignore index 4cf864c..5a1533e 100644 --- a/.gitignore +++ b/.gitignore @@ -96,4 +96,6 @@ files fly.toml fly-deploy.yml Dockerfile -cookies.txt \ No newline at end of file +cookies.txt + +fcm_secret.json \ No newline at end of file diff --git a/eversync/settings.py b/eversync/settings.py index a0a1075..a400631 100644 --- a/eversync/settings.py +++ b/eversync/settings.py @@ -29,6 +29,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.getenv('SECRET_KEY') +FCM_SERVICE_ACCOUNT = str(BASE_DIR / "file.json") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True diff --git a/eversyncc/models.py b/eversyncc/models.py index 4d51496..5d71ecb 100644 --- a/eversyncc/models.py +++ b/eversyncc/models.py @@ -3,6 +3,13 @@ from django.db import models # Create your models here. from django.contrib.auth.models import User +class UserNotifs(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + device_token = models.CharField(blank=True, null=True) + + def __str__(self): + return f"{self.user.username}'s notif token: {self.device_token[:8]}..." + class UserStorage(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) storage_limit = models.BigIntegerField(default=5 * 1024 * 1024 * 1024) # 5 GB diff --git a/eversyncc/signals.py b/eversyncc/signals.py index e372e2b..aabdcc2 100644 --- a/eversyncc/signals.py +++ b/eversyncc/signals.py @@ -1,9 +1,10 @@ from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth.models import User -from .models import UserStorage +from .models import UserStorage, UserNotifs @receiver(post_save, sender=User) def create_user_storage(sender, instance, created, **kwargs): if created: - UserStorage.objects.create(user=instance) \ No newline at end of file + UserStorage.objects.create(user=instance) + UserNotifs.objects.create(user=instance) \ No newline at end of file diff --git a/eversyncc/urls.py b/eversyncc/urls.py index 2d1203c..cb3a258 100644 --- a/eversyncc/urls.py +++ b/eversyncc/urls.py @@ -69,6 +69,8 @@ urlpatterns = [ path('whiteboard//save-images/', views.save_images, name='save_images'), path('whiteboard//upload-image/', views.upload_image, name='upload_image'), path('whiteboard//delete-image/', views.delete_image, name='delete_image'), + path('api/update_device_token/', views.update_device_token, name='update_device_token'), + diff --git a/eversyncc/views.py b/eversyncc/views.py index d15a81a..87fa024 100644 --- a/eversyncc/views.py +++ b/eversyncc/views.py @@ -35,7 +35,8 @@ from .forms import RegisterForm from django.core.files.storage import FileSystemStorage import uuid from django.conf import settings - +from fcm import send_push_notification +from .models import UserNotifs def email_verified_required(view_func): @wraps(view_func) @@ -584,6 +585,12 @@ def send_message(request): content = request.POST.get('content') receiver = User.objects.get(username=reciever_username) message = Message.objects.create(sender=request.user, receiver=receiver, content=content) + if hasattr(receiver, 'device_token') and receiver.device_token: + token = receiver.device_token + title = f"New message from {request.user.username}" + body = content[:100] + send_push_notification(token, title, body) + return JsonResponse({"message": "sent", "message_id": message.id}) else: return JsonResponse({"message": "error"}) @@ -968,4 +975,20 @@ def delete_image(request, whiteboard_id): except Exception as e: return JsonResponse({'error': str(e)}, status=500) - return JsonResponse({'error': 'Invalid request method'}, status=405) \ No newline at end of file + return JsonResponse({'error': 'Invalid request method'}, status=405) + + +def update_device_token(request): + try: + data = json.loads(request.body) + token = data.get('device_token', '').strip() + if not token: + return JsonResponse({"error": "No device_token provided"}, status=400) + + user_notifs, created = UserNotifs.objects.get_or_create(user=request.user) + user_notifs.device_token = token + user_notifs.save() + + return JsonResponse({"message": "Device token updated"}) + except Exception as e: + return JsonResponse({"error": str(e)}, status=500) \ No newline at end of file diff --git a/fcm.py b/fcm.py new file mode 100644 index 0000000..9c56b6c --- /dev/null +++ b/fcm.py @@ -0,0 +1,33 @@ +import json +import requests +from google.oauth2 import service_account +from django.conf import settings + +def send_push_notification(token, title, body, data=None): + creds = service_account.Credentials.from_service_account_file( + settings.FCM_SERVICE_ACCOUNT, + scopes=["https://www.googleapis.com/auth/firebase.messaging"] + ) + + creds.refresh(requests.Request()) + + headers = { + "Authorization": f"Bearer {creds.token}", + "Content-Type": "application/json; UTF-8", + } + + url = f"https://fcm.googleapis.com/v1/projects/{creds.project_id}/messages:send" + + message = { + "message": { + "token": token, + "notification": { + "title": title, + "body": body, + }, + "data": data or {} + } + } + + response = requests.post(url, headers=headers, data=json.dumps(message)) + return response.json() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 62611b8..d7d48bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,4 +22,5 @@ icalendar sentry-sdk[django] yt-dlp sendgrid -django-recaptcha \ No newline at end of file +django-recaptcha +google-auth \ No newline at end of file diff --git a/sentry_replay.html b/sentry_replay.html index 65669a9..491b4b7 100644 --- a/sentry_replay.html +++ b/sentry_replay.html @@ -37,4 +37,85 @@ const replay = Sentry.getReplay(); replay.start(); + + + + + + + \ No newline at end of file