Jun 24 - Implement devices tracker
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# app/routes/devices.py
|
||||
# ----------------------
|
||||
# Admin-only page for viewing registered iOS devices and pushing
|
||||
# "please update" notifications to users on outdated app versions.
|
||||
|
||||
import logging
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from app import db
|
||||
from app.models.device_registration import DeviceRegistration
|
||||
from app.models.notification import Notification
|
||||
from app.utils.decorators import admin_required
|
||||
from app.utils.audit import log_action, ACTION_CREATE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
bp = Blueprint('devices', __name__, url_prefix='/admin/devices')
|
||||
|
||||
|
||||
@bp.route('/', methods=['GET'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def index():
|
||||
"""Show all registered devices, most-recently-seen first."""
|
||||
devices = (
|
||||
DeviceRegistration.query
|
||||
.order_by(DeviceRegistration.last_seen_at.desc())
|
||||
.all()
|
||||
)
|
||||
return render_template('admin/devices.html', devices=devices)
|
||||
|
||||
|
||||
@bp.route('/notify', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def notify_update():
|
||||
"""
|
||||
Send an in-app update notice to all users whose app_version is below
|
||||
the version string entered by the admin.
|
||||
|
||||
Form fields
|
||||
-----------
|
||||
current_version str The version string to treat as current (e.g. "1.3.0")
|
||||
message str Optional custom message body (default provided)
|
||||
"""
|
||||
current_version = (request.form.get('current_version') or '').strip()
|
||||
custom_message = (request.form.get('message') or '').strip()
|
||||
|
||||
if not current_version:
|
||||
flash('Current version is required.', 'danger')
|
||||
return redirect(url_for('devices.index'))
|
||||
|
||||
def version_tuple(v: str):
|
||||
"""Convert "1.3.0" → (1, 3, 0) for comparison. Non-numeric parts → 0."""
|
||||
try:
|
||||
return tuple(int(x) for x in v.strip().split('.'))
|
||||
except ValueError:
|
||||
return (0,)
|
||||
|
||||
target_v = version_tuple(current_version)
|
||||
|
||||
# Find all devices running an older version
|
||||
all_devices = DeviceRegistration.query.all()
|
||||
outdated = [d for d in all_devices if version_tuple(d.app_version) < target_v]
|
||||
|
||||
if not outdated:
|
||||
flash(f'No devices found running a version older than {current_version}.', 'info')
|
||||
return redirect(url_for('devices.index'))
|
||||
|
||||
# Deduplicate by user_id — one notification per user even if they have
|
||||
# multiple devices registered (e.g. primary + secondary server devices).
|
||||
seen_users = set()
|
||||
notified = 0
|
||||
for device in outdated:
|
||||
if device.user_id in seen_users:
|
||||
continue
|
||||
seen_users.add(device.user_id)
|
||||
|
||||
body = custom_message or (
|
||||
f'A new version of JanitorialQC ({current_version}) is available. '
|
||||
f'Please update from the App Store to get the latest features and fixes.'
|
||||
)
|
||||
notif = Notification(
|
||||
user_id = device.user_id,
|
||||
title = f'App Update Available — v{current_version}',
|
||||
body = body,
|
||||
link = None,
|
||||
event_type = 'admin_broadcast',
|
||||
is_read = False,
|
||||
)
|
||||
db.session.add(notif)
|
||||
notified += 1
|
||||
|
||||
db.session.commit()
|
||||
|
||||
log_action(ACTION_CREATE, 'DeviceUpdateNotice', 0,
|
||||
f'v{current_version} notice → {notified} user(s)',
|
||||
f'outdated_devices={len(outdated)}; sent_by={current_user.username}')
|
||||
|
||||
logger.info('DEVICES | update_notice | version=%s | users_notified=%d | devices_outdated=%d | by=%s',
|
||||
current_version, notified, len(outdated), current_user.username)
|
||||
|
||||
flash(
|
||||
f'Update notice sent to {notified} user(s) on {len(outdated)} outdated device(s).',
|
||||
'success'
|
||||
)
|
||||
return redirect(url_for('devices.index'))
|
||||
Reference in New Issue
Block a user