mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-10-05 13:35:23 +00:00
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
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 4m17s
This commit is contained in:
70
api/tasks.py
70
api/tasks.py
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user