Add personal API tokens; wire measuring tools into remaining surfaces
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:
@@ -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)
|
||||
# =============================================================================
|
||||
|
||||
@@ -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}',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user