99 lines
3.2 KiB
Python
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')) |