fix(tasks): add device activation/deactivation & blocking/unblocking for background task 🐛
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 4m17s

This commit is contained in:
2025-07-26 20:33:26 +05:00
parent 72e0cd1fba
commit 3e7a74950e
2 changed files with 91 additions and 16 deletions

View File

@@ -11,6 +11,7 @@ from api.omada import Omada
from apibase.env import env, BASE_DIR from apibase.env import env, BASE_DIR
from procrastinate.contrib.django import app from procrastinate.contrib.django import app
from procrastinate import builtin_tasks from procrastinate import builtin_tasks
import time
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -36,22 +37,64 @@ async def remove_old_jobs(context, timestamp):
@app.periodic( @app.periodic(
cron="0 0 */28 * *", queue="heavy_tasks", periodic_id="deactivate_expired_devices" cron="0 22 * * *",
queue="heavy_tasks",
periodic_id="deactivate_expired_devices_and_block_in_omada",
) # type: ignore ) # type: ignore
@app.task @app.task
def deactivate_expired_devices(): def deactivate_expired_devices_and_block_in_omada():
expired_devices = Device.objects.filter( expired_devices = Device.objects.filter(
expiry_date__lte=timezone.localtime(timezone.now()), is_active=True expiry_date__lte=timezone.localtime(timezone.now()), is_active=True
).select_related("user") ).select_related("user")
print("Expired Devices: ", expired_devices) print("Expired Devices: ", expired_devices)
count = expired_devices.count() count = expired_devices.count()
if count == 0:
return {"total_expired_devices": 0}
user_devices_map = {} user_devices_map = {}
devices_successfully_blocked = []
devices_failed_to_block = []
omada_client = Omada()
# Single loop to collect data and block devices
for device in expired_devices: for device in expired_devices:
# Collect devices for SMS notifications
if device.user and device.user.mobile: if device.user and device.user.mobile:
if device.user.mobile not in user_devices_map: if device.user.mobile not in user_devices_map:
user_devices_map[device.user.mobile] = [] user_devices_map[device.user.mobile] = []
user_devices_map[device.user.mobile].append(device.name) user_devices_map[device.user.mobile].append(device.name)
# Try to block device in Omada
try:
omada_client.block_device(mac_address=device.mac, operation="block")
# Only prepare for update if Omada blocking succeeded
device.blocked = True
device.is_active = False
devices_successfully_blocked.append(device)
logger.info(f"Successfully blocked device {device.mac} in Omada")
time.sleep(20) # Sleep to avoid rate limiting
except Exception as e:
logger.error(f"Failed to block device [omada] {device.mac}: {e}")
devices_failed_to_block.append(device)
# Continue to next device without updating this one
# Bulk update only successfully blocked devices
if devices_successfully_blocked:
try:
Device.objects.bulk_update(
devices_successfully_blocked, ["is_active", "blocked"]
)
logger.info(
f"Successfully updated {len(devices_successfully_blocked)} devices in database"
)
except Exception as e:
logger.error(f"Failed to bulk update devices in database: {e}")
# You might want to handle this case - devices are blocked in Omada but not updated in DB
# Send SMS notifications
sms_count = 0
for mobile, device_names in user_devices_map.items(): for mobile, device_names in user_devices_map.items():
if not mobile: if not mobile:
continue continue
@@ -59,14 +102,25 @@ def deactivate_expired_devices():
[f"{i + 1}. {name}" for i, name in enumerate(device_names)] [f"{i + 1}. {name}" for i, name in enumerate(device_names)]
) )
print("device list: ", device_list) print("device list: ", device_list)
send_sms( try:
mobile, send_sms(
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", 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.") sms_count += 1
except Exception as e:
logger.error(f"Failed to send SMS to {mobile}: {e}")
print(f"Total {count} expired devices processed.")
print(f"Successfully blocked: {len(devices_successfully_blocked)}")
print(f"Failed to block: {len(devices_failed_to_block)}")
print(f"SMS notifications sent: {sms_count}")
return { return {
"total_expired_devices": count, "total_expired_devices": count,
"successfully_blocked": len(devices_successfully_blocked),
"failed_to_block": len(devices_failed_to_block),
"sms_sent": sms_count,
} }

View File

@@ -27,6 +27,7 @@ from .filters import PaymentFilter, TopupFilter, WalletTransactionFilter
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from typing import Optional from typing import Optional
from api.models import User from api.models import User
from api.omada import Omada
env.read_env(os.path.join(BASE_DIR, ".env")) env.read_env(os.path.join(BASE_DIR, ".env"))
@@ -77,10 +78,10 @@ class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIVi
def create(self, request): def create(self, request):
data = request.data data = request.data
user = request.user user = request.user
amount = data.get("amount")
number_of_months = data.get("number_of_months") number_of_months = data.get("number_of_months")
number_of_devices = 0
device_ids = data.get("device_ids", []) device_ids = data.get("device_ids", [])
print(amount, number_of_months, device_ids) print(number_of_months, device_ids)
current_time = timezone.now() current_time = timezone.now()
expires_at = current_time + timedelta(minutes=10) expires_at = current_time + timedelta(minutes=10)
for device_id in device_ids: for device_id in device_ids:
@@ -91,9 +92,10 @@ class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIVi
{"message": f"Device with id {device_id} not found."}, {"message": f"Device with id {device_id} not found."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
if not amount or not number_of_months: number_of_devices += 1
if not number_of_months:
return Response( return Response(
{"message": "amount and number_of_months are required."}, {"message": "number_of_months is required."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
if not device_ids: if not device_ids:
@@ -102,6 +104,7 @@ class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIVi
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
# Create payment # Create payment
amount = 100
payment = Payment.objects.create( payment = Payment.objects.create(
amount=amount, amount=amount,
number_of_months=number_of_months, number_of_months=number_of_months,
@@ -180,8 +183,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
devices = payment.devices.all() devices = payment.devices.all()
data = request.data data = request.data
user = request.user user = request.user
print("logged in user", user) omada_client = Omada()
print("Payment user", payment.user)
if payment.paid: if payment.paid:
return Response( return Response(
{"message": "Payment has already been verified."}, {"message": "Payment has already been verified."},
@@ -211,6 +213,23 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
payment, payment,
devices, devices,
) )
device_list = []
for device in devices:
device_list.append(
{
"mac": device.mac,
"name": device.name,
}
)
if device.registered:
omada_client.block_device(
mac_address=device.mac, operation="unblock"
)
if not device.registered:
# Add to omada
add_new_devices_to_omada.defer(new_devices=device_list)
device.registered = True
device.save()
return Response( return Response(
{ {
"status": True, "status": True,
@@ -233,7 +252,6 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
is_active=True, is_active=True,
expiry_date=expiry_date, expiry_date=expiry_date,
has_a_pending_payment=False, has_a_pending_payment=False,
registered=True,
) )
payment.status = "PAID" payment.status = "PAID"
payment.save() payment.save()
@@ -246,6 +264,10 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
"name": device.name, "name": device.name,
} }
) )
if device.registered:
omada_client.block_device(
mac_address=device.mac, operation="unblock"
)
if not device.registered: if not device.registered:
# Add to omada # Add to omada
add_new_devices_to_omada.defer(new_devices=device_list) add_new_devices_to_omada.defer(new_devices=device_list)
@@ -288,7 +310,6 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
is_active=True, is_active=True,
expiry_date=expiry_date, expiry_date=expiry_date,
has_a_pending_payment=False, has_a_pending_payment=False,
registered=True,
) )
payment.save() payment.save()