05/15 Update: implement iPad notification function 2

This commit is contained in:
Nguyen Ngo
2026-05-15 15:15:10 -04:00
parent b761f6577e
commit b69a52e5f5
5 changed files with 162 additions and 13 deletions
+86 -13
View File
@@ -3,6 +3,10 @@ app/api/issues.py
-----------------
Mobile API endpoint for submitting issues from the iPad app.
GET /api/v1/issues
Returns issues assigned to the authenticated inspector (or all for admin/director).
Used by the iPad to display assigned issues that were created via the web portal.
POST /api/v1/issues
Creates a new issue record.
Accepts a mobile_local_id for idempotency.
@@ -40,7 +44,87 @@ _VALID_SEVERITY = {'low', 'medium', 'high', 'critical'}
_VALID_STATUSES = {'open', 'in_progress', 'resolved', 'pending_verification'}
# ── Create Issue ──────────────────────────────────────────────────────────────
def _issue_payload(issue):
"""Serialise an Issue to the dict returned in list/detail responses."""
facility = issue.resolved_facility
return {
'id': issue.id,
'status': issue.status,
'severity': issue.severity,
'description': issue.description,
'assigned_to': issue.assigned_to,
'facility_id': facility.id if facility else None,
'facility_name': facility.name if facility else None,
'reported_at': issue.reported_at.isoformat() if issue.reported_at else None,
'resolved_at': issue.resolved_at.isoformat() if issue.resolved_at else None,
'mobile_local_id': issue.mobile_local_id,
}
# ── List Assigned Issues ──────────────────────────────────────────────────────
@bp.route('/issues', methods=['GET'])
@jwt_required
def list_issues():
"""
Return issues assigned to the authenticated user.
Inspectors: only issues where assigned_to == current user.
Admin / director / project_manager: all non-resolved issues (capped at 200).
Query parameters
----------------
status str Filter by status (default: excludes resolved).
limit int Default 100, max 200.
offset int Default 0.
Response 200
------------
{
"ok": true,
"data": {
"issues": [...],
"total": 12,
"limit": 100,
"offset": 0
}
}
"""
user = g.api_user
if user.role not in _ALLOWED_ROLES:
return api_error('Access denied', 403)
limit = min(int(request.args.get('limit', 100)), 200)
offset = max(int(request.args.get('offset', 0)), 0)
query = Issue.query
if user.role == 'inspector':
# Inspectors only see issues assigned to them
query = query.filter(Issue.assigned_to == user.id)
else:
# Broader roles: exclude resolved by default so the list stays manageable
status_filter = request.args.get('status')
if status_filter:
query = query.filter(Issue.status == status_filter)
else:
query = query.filter(Issue.status != 'resolved')
total = query.count()
issues = (
query
.order_by(Issue.reported_at.desc())
.offset(offset)
.limit(limit)
.all()
)
payload = [_issue_payload(i) for i in issues]
logger.info('API ISSUES | list | user=%s | count=%d | total=%d',
user.username, len(payload), total)
return api_ok({'issues': payload, 'total': total, 'limit': limit, 'offset': offset})
@bp.route('/issues', methods=['POST'])
@jwt_required
@@ -180,18 +264,7 @@ def get_issue(issue_id):
if user.role == 'inspector' and issue.assigned_to != user.id:
return api_error('Access denied', 403)
facility = issue.resolved_facility
return api_ok({
'id': issue.id,
'status': issue.status,
'severity': issue.severity,
'description': issue.description,
'assigned_to': issue.assigned_to,
'facility_id': facility.id if facility else None,
'facility_name': facility.name if facility else None,
'reported_at': issue.reported_at.isoformat() if issue.reported_at else None,
'resolved_at': issue.resolved_at.isoformat() if issue.resolved_at else None,
})
return api_ok(_issue_payload(issue))
# ── Update Issue Status ───────────────────────────────────────────────────────