Add personal API tokens; wire measuring tools into remaining surfaces
Some checks failed
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / backend (push) Has been cancelled

API tokens: any user mints named, optionally-expiring tokens
(shopdb_pat_..., sha256-stored, secret shown once) at Settings > API
Tokens; a before-request shim swaps a valid PAT for a request-scoped
JWT of its owner, so the entire existing auth/authz/import-mode stack
works unchanged and revoked/expired tokens 401 cleanly. Built for
long-running scripts - the legacy import no longer dies when a login
JWT expires. Migration 7d21_apitokens; create/revoke audit-logged.

Audited integration gaps fixed: Asset.to_dict serializes measuring
tools (typedata + pluginid - relationship links to tools resolve); map
subtype filter/colors and MapEditor include them; dashboard totals
count them; warranty links use a new by-asset route; the measuringtools
ADR-010 hooks are real (corrected presentation token, implemented
map-overlay endpoint); the login avatar resolves through the
employee-photo helper.

737 tests pass; naming green; frontend builds; both features verified
live end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 08:33:02 -04:00
parent 64a5abdb08
commit da86b3ae0c
31 changed files with 1197 additions and 38 deletions

View File

@@ -348,6 +348,32 @@ def delete_tool(tool_id: int):
return success_response(message='Measuring tool deleted')
# =============================================================================
# Map overlay (ADR-010 calibration-due badge)
# =============================================================================
@measuringtools_bp.route('/map-overlay', methods=['GET'])
@jwt_required(optional=True)
def map_overlay():
"""Calibration-status overlay for active measuring tools.
The map places markers itself from the assets feed; this overlay only
supplies the per-asset calibration decoration. Consumers join by assetid,
so no map coordinates are returned. Status is derived at read time.
"""
today = date.today()
query = db.session.query(MeasuringTool).join(Asset).filter(Asset.isactive == True)
data = []
for tool in query.all():
status = derive_status(tool.nextcalibrationdate, today)
data.append({
'assetid': tool.assetid,
'calibrationstatus': status,
'statuscolor': STATUS_COLORS.get(status, STATUS_COLORS['unknown']),
})
return success_response(data)
# =============================================================================
# Calibration report (for the Reports hub)
# =============================================================================

View File

@@ -112,12 +112,15 @@ class MeasuringToolsPlugin(BasePlugin):
def get_asset_presentation(self) -> List[Dict]:
# ADR-010 pilot. Tells core how to render + link the measuring_tool
# asset type in global-search rows and cross-links.
# The consumer only substitutes {assetid} (search/cross-link rows carry
# the core asset id, not the extension id), so link through the by-asset
# resolver route rather than the id-keyed detail route.
return [
{
'assettype': 'measuring_tool',
'icon': 'ruler',
'label': 'Measuring Tool',
'route': '/measuringtools/{assetid}',
'route': '/measuringtools/by-asset/{assetid}',
},
]