96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
"""
|
|
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})
|