""" app/api/devices.py ------------------ Mobile API endpoint for device registration. POST /api/v1/devices/register Upserts a device record for the authenticated user. Called on every app foreground (active scenePhase) so last_seen_at stays current and the admin can identify stale / outdated installs. Request JSON ------------ { "device_id": "stable-uuid-from-keychain", // required "device_name": "Nguyen's iPad", // UIDevice.current.name "app_version": "1.2.0", // CFBundleShortVersionString "ios_version": "18.3.1" // UIDevice.current.systemVersion } Response 200 ------------ { "ok": true, "data": { "registered": true } } """ import logging from flask import Blueprint, request, g from app import db from app.models.device_registration import DeviceRegistration from app.api.errors import api_ok, api_error from app.api.decorators import jwt_required from app.utils.audit import log_action, ACTION_UPDATE, ACTION_CREATE from app.utils.time_utils import now_eastern logger = logging.getLogger(__name__) bp = Blueprint('api_devices', __name__) _ALLOWED_ROLES = {'admin', 'director', 'inspector', 'project_manager'} @bp.route('/devices/register', methods=['POST']) @jwt_required def register_device(): user = g.api_user if user.role not in _ALLOWED_ROLES: return api_error('Access denied', 403) data = request.get_json(silent=True) or {} device_id = (data.get('device_id') or '').strip() device_name = (data.get('device_name') or '').strip()[:255] app_version = (data.get('app_version') or '').strip()[:32] ios_version = (data.get('ios_version') or '').strip()[:32] if not device_id: return api_error('device_id is required', 400) if len(device_id) > 64: return api_error('device_id too long', 400) now = now_eastern() existing = DeviceRegistration.query.filter_by(device_id=device_id).first() if existing: # Update — always refresh last_seen_at and app/ios version existing.user_id = user.id # re-bind if different user logs in same device existing.device_name = device_name or existing.device_name existing.app_version = app_version or existing.app_version existing.ios_version = ios_version or existing.ios_version existing.last_seen_at = now db.session.commit() log_action(ACTION_UPDATE, 'DeviceRegistration', existing.id, f'{device_name} v{app_version}', f'user={user.username}; ios={ios_version}') logger.info('API DEVICES | updated | device_id=%s | user=%s | app=%s', device_id[:8], user.username, app_version) else: reg = DeviceRegistration( device_id = device_id, user_id = user.id, device_name = device_name, app_version = app_version, ios_version = ios_version, registered_at = now, last_seen_at = now, ) db.session.add(reg) db.session.commit() log_action(ACTION_CREATE, 'DeviceRegistration', reg.id, f'{device_name} v{app_version}', f'user={user.username}; ios={ios_version}') logger.info('API DEVICES | registered | device_id=%s | user=%s | app=%s', device_id[:8], user.username, app_version) return api_ok({'registered': True})