diff --git a/.vscode/settings.json b/.vscode/settings.json index f961bde..cb48af9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,6 @@ ], "python.testing.pytestEnabled": false, "python.testing.unittestEnabled": true, - "postman.settings.dotenv-detection-notification-visibility": false + "postman.settings.dotenv-detection-notification-visibility": false, + "python.analysis.autoImportCompletions": true } \ No newline at end of file diff --git a/api/sms.py b/api/sms.py index 52fc234..71c83b9 100644 --- a/api/sms.py +++ b/api/sms.py @@ -4,8 +4,13 @@ import json import logging logger = logging.getLogger(__name__) -api_url = config("SMS_API_URL", default="") -api_key = config("SMS_API_KEY", default="") +api_url = str(config("SMS_API_URL", cast=str, default="")) +api_key = str(config("SMS_API_KEY", cast=str, default="")) + +if not api_url or not api_key: + raise ValueError( + "SMS_API_URL and SMS_API_KEY must be set in the environment variables." + ) def send_otp(mobile: str, otp: int, message: str): diff --git a/api/tasks.py b/api/tasks.py index 2c0a489..461e01f 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -1,16 +1,57 @@ from django.shortcuts import get_object_or_404 from api.models import User +from devices.models import Device from api.sms import send_sms import requests from apibase.env import env, BASE_DIR import os import logging +from celery import shared_task +from django.utils import timezone logger = logging.getLogger(__name__) env.read_env(os.path.join(BASE_DIR, ".env")) -PEOPLE_API_URL = env.str("PEOPLE_API_URL", "") +PERSON_VERIFY_BASE_URL = env.str("PERSON_VERIFY_BASE_URL") + + +@shared_task +def add(x, y): + print(f"Adding {x} and {y}") + return x + y + + +@shared_task +def deactivate_expired_devices(): + expired_devices = Device.objects.filter( + expiry_date__lte=timezone.localtime(timezone.now()), is_active=True + ).select_related("user") + print("Expired Devices: ", expired_devices) + count = expired_devices.count() + user_devices_map = {} + for device in expired_devices: + if device.user and device.user.mobile: + if device.user.mobile not in user_devices_map: + user_devices_map[device.user.mobile] = [] + user_devices_map[device.user.mobile].append(device.name) + + for mobile, device_names in user_devices_map.items(): + if not mobile: + continue + device_list = "\n".join( + [f"{i + 1}. {name}" for i, name in enumerate(device_names)] + ) + print("device list: ", device_list) + send_sms( + mobile, + f"Dear {mobile}, \n\nThe following devices have expired: \n{device_list}. \n\nPlease make a payment to keep your devices active. \n\n- SAR Link", + ) + # expired_devices.update(is_active=False) + print(f"Total {count} expired devices.") + return { + "total_expired_devices": count, + } def verify_user_with_person_api_task(user_id: int): @@ -21,9 +62,11 @@ def verify_user_with_person_api_task(user_id: int): user = get_object_or_404(User, id=user_id) # Call the Person API to verify the user - if not PEOPLE_API_URL: - raise ValueError("PEOPLE_API_URL is not set in the environment variables.") - response = requests.get(f"{PEOPLE_API_URL}/api/person/{user.id_card}") + if not PERSON_VERIFY_BASE_URL: + raise ValueError( + "PERSON_VERIFY_BASE_URL is not set in the environment variables." + ) + response = requests.get(f"{PERSON_VERIFY_BASE_URL}/api/person/{user.id_card}") if response.status_code == 200: data = response.json() api_nic = data.get("nic") @@ -33,6 +76,13 @@ def verify_user_with_person_api_task(user_id: int): api_atoll = data.get("atoll_en") api_island_name = data.get("island_name_en") + if not user.mobile or user.dob is None: + logger.error("User mobile or date of birth is not set.") + return None + if not user.island or user.atoll is None: + logger.error("User island or atoll is not set.") + return None + logger.info(f"API nic: {api_nic}") logger.info(f"API name: {api_name}") logger.info(f"API house name: {api_house_name}") @@ -77,6 +127,7 @@ def verify_user_with_person_api_task(user_id: int): else: user.verified = False user.save() + send_sms( user.mobile, f"Dear {user.first_name} {user.last_name}, \n\nYour account registration is being processed. \n\nWe will notify you once verification is complete. \n\n - SAR Link", diff --git a/api/views.py b/api/views.py index 41a6b7c..5763152 100644 --- a/api/views.py +++ b/api/views.py @@ -31,7 +31,7 @@ from typing import cast, Dict, Any from django.core.mail import send_mail from django.db.models import Q from api.sms import send_otp - +from .tasks import add, deactivate_expired_devices # local apps import from .serializers import ( @@ -57,6 +57,13 @@ class ErrorMessages: UNDERAGE_ERROR = "You must be 18 and above to signup." +@api_view(["GET"]) +def healthcheck(request): + add.delay(1, 2) + deactivate_expired_devices.delay() + return Response({"status": "Good"}, status=status.HTTP_200_OK) + + class UpdateUserWalletView(generics.UpdateAPIView): # Create user API view serializer_class = CustomUserByWalletBalanceSerializer @@ -416,11 +423,6 @@ class UserDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView): return Response(data) -@api_view(["GET"]) -def healthcheck(request): - return Response({"status": "Good"}, status=status.HTTP_200_OK) - - @api_view(["POST"]) @permission_classes((permissions.AllowAny,)) def test_email(request): diff --git a/apibase/__init__.py b/apibase/__init__.py index e69de29..a0711ca 100644 --- a/apibase/__init__.py +++ b/apibase/__init__.py @@ -0,0 +1,4 @@ +# myproject/__init__.py +from .celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/apibase/celery.py b/apibase/celery.py new file mode 100644 index 0000000..3531d90 --- /dev/null +++ b/apibase/celery.py @@ -0,0 +1,22 @@ +import os + +from celery import Celery + +# Set the default Django settings module for the 'celery' program. +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "apibase.settings") + +app = Celery("apibase") + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object("django.conf:settings", namespace="CELERY") + +# Load task modules from all registered Django apps. +app.autodiscover_tasks(["api", "devices"]) + + +@app.task(bind=True, ignore_result=True) +def debug_task(self): + print(f"Request: {self.request!r}") diff --git a/apibase/settings.py b/apibase/settings.py index dafe0a3..ef80b65 100644 --- a/apibase/settings.py +++ b/apibase/settings.py @@ -363,3 +363,10 @@ PASSWORDLESS_AUTH = { "PASSWORDLESS_REGISTER_NEW_USERS": True, "PASSWORDLESS_EMAIL_NOREPLY_ADDRESS": "noreply@sarlink.net", } + + +# CELERY CONFIGURATION +CELERY_BROKER_URL = "redis://localhost:6379/0" # or your Redis URL +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_BACKEND = "redis://localhost:6379/0" diff --git a/pyrightconfig.json b/pyrightconfig.json index ce02071..b4b408d 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -3,7 +3,7 @@ "venv": ".venv", "reportMissingImports": "error", "include": ["src"], - "typeCheckingMode": "off", + "typeCheckingMode": "standard", "exclude": [ "council-api/**/migrations", "**/__pycache__",