Files

99 lines
3.2 KiB
Python

# 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.api_token import DeviceToken
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 = (
DeviceToken.query
.order_by(db.func.coalesce(DeviceToken.last_seen_at, DeviceToken.registered_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.
"""
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):
try:
return tuple(int(x) for x in v.strip().split('.'))
except ValueError:
return (0,)
target_v = version_tuple(current_version)
all_devices = DeviceToken.query.all()
outdated = [d for d in all_devices if version_tuple(d.app_version or '0') < target_v]
if not outdated:
flash(f'No devices found running a version older than {current_version}.', 'info')
return redirect(url_for('devices.index'))
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 | by=%s',
current_version, notified, 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'))