fix: Hasura camelCase relations + UI patch + NodeRED
- Fix Evses.evseId type (varchar→integer) for FK compatibility - Recreate all Hasura relations with camelCase names matching UI expectations - Patch UI .next JS files: PascalCase→camelCase (Evses, Connectors, etc.) - Add fix-hasura-relations.py script for future maintenance - NodeRED accessible at nodered.digitribe.fr - All 15 stations online with working map and detail pages
This commit is contained in:
241
scripts/fix-hasura-relations.py
Normal file
241
scripts/fix-hasura-relations.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script pour recréer toutes les relations Hasura avec les noms camelCase.
|
||||||
|
Utilise l'API metadata Hasura pour créer les relations manquantes.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ADMIN_SECRET = 'Digitribe972'
|
||||||
|
METADATA_URL = 'http://localhost:8082/v1/metadata'
|
||||||
|
GRAPHQL_URL = 'http://localhost:8082/v1/graphql'
|
||||||
|
|
||||||
|
def graphql_query(query):
|
||||||
|
"""Execute a GraphQL query and return the result."""
|
||||||
|
r = subprocess.run(
|
||||||
|
['curl', '-s', '-X', 'POST', GRAPHQL_URL,
|
||||||
|
'-H', f'x-hasura-admin-secret: {ADMIN_SECRET}',
|
||||||
|
'-H', 'Content-Type: application/json',
|
||||||
|
'-d', json.dumps({"query": query})],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
)
|
||||||
|
return json.loads(r.stdout)
|
||||||
|
|
||||||
|
def metadata_command(payload):
|
||||||
|
"""Execute a metadata command and return the result."""
|
||||||
|
r = subprocess.run(
|
||||||
|
['curl', '-s', '-X', 'POST', METADATA_URL,
|
||||||
|
'-H', f'x-hasura-admin-secret: {ADMIN_SECRET}',
|
||||||
|
'-H', 'Content-Type: application/json',
|
||||||
|
'-d', json.dumps(payload)],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
)
|
||||||
|
return json.loads(r.stdout)
|
||||||
|
|
||||||
|
def test_relation(table, relation, fields='{ id }'):
|
||||||
|
"""Test if a relation works by querying it."""
|
||||||
|
query = f'{{ {table}(where: {{id: {{_eq: "CP001"}}}}) {{ id {relation} {fields} }} }}'
|
||||||
|
result = graphql_query(query)
|
||||||
|
if 'errors' in result:
|
||||||
|
return False, result['errors'][0]['message'][:100]
|
||||||
|
return True, "OK"
|
||||||
|
|
||||||
|
def create_relation(table, name, rel_type, remote_table, col_map):
|
||||||
|
"""Create a relationship in Hasura."""
|
||||||
|
if rel_type == 'object':
|
||||||
|
payload = {
|
||||||
|
"type": "pg_create_object_relationship",
|
||||||
|
"args": {
|
||||||
|
"source": "default",
|
||||||
|
"table": {"schema": "public", "name": table},
|
||||||
|
"name": name,
|
||||||
|
"using": {
|
||||||
|
"manual_configuration": {
|
||||||
|
"remote_table": {"schema": "public", "name": remote_table},
|
||||||
|
"column_mapping": col_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
payload = {
|
||||||
|
"type": "pg_create_array_relationship",
|
||||||
|
"args": {
|
||||||
|
"source": "default",
|
||||||
|
"table": {"schema": "public", "name": table},
|
||||||
|
"name": name,
|
||||||
|
"using": {
|
||||||
|
"manual_configuration": {
|
||||||
|
"remote_table": {"schema": "public", "name": remote_table},
|
||||||
|
"column_mapping": col_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = metadata_command(payload)
|
||||||
|
if 'message' in result and result['message'] == 'success':
|
||||||
|
return True
|
||||||
|
elif 'error' in result:
|
||||||
|
if 'already exists' in result['error']:
|
||||||
|
return True # Already exists, that's OK
|
||||||
|
print(f" ERROR: {result['error'][:200]}")
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=== Diagnostic des relations Hasura ===\n")
|
||||||
|
|
||||||
|
# Test critical relations
|
||||||
|
critical_relations = [
|
||||||
|
("ChargingStations", "location", "{ id name }"),
|
||||||
|
("ChargingStations", "evses", "{ id evseId }"),
|
||||||
|
("ChargingStations", "connectors", "{ id status }"),
|
||||||
|
("ChargingStations", "transactions", "{ id }"),
|
||||||
|
("ChargingStations", "latestStatusNotifications", "{ statusNotification { connectorStatus } }"),
|
||||||
|
("ChargingStations", "tenant", "{ id name }"),
|
||||||
|
("Evses", "connectors", "{ id status }"),
|
||||||
|
("Transactions", "chargingStation", "{ id }"),
|
||||||
|
("Transactions", "evse", "{ id }"),
|
||||||
|
("Transactions", "connector", "{ id }"),
|
||||||
|
("Transactions", "location", "{ id name }"),
|
||||||
|
("LatestStatusNotifications", "statusNotification", "{ connectorStatus }"),
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Test des relations critiques:")
|
||||||
|
all_ok = True
|
||||||
|
for table, relation, fields in critical_relations:
|
||||||
|
ok, msg = test_relation(table, relation, fields)
|
||||||
|
status = "OK" if ok else "FAIL"
|
||||||
|
print(f" {status}: {table}.{relation}")
|
||||||
|
if not ok:
|
||||||
|
all_ok = False
|
||||||
|
print(f" -> {msg}")
|
||||||
|
|
||||||
|
if all_ok:
|
||||||
|
print("\n=== Toutes les relations fonctionnent ! ===")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n=== Recréation des relations manquantes ===\n")
|
||||||
|
|
||||||
|
# Define all relations to create
|
||||||
|
relations = [
|
||||||
|
# ChargingStations
|
||||||
|
("ChargingStations", "location", "object", "Locations", {"locationId": "id"}),
|
||||||
|
("ChargingStations", "tenant", "object", "Tenants", {"tenantId": "id"}),
|
||||||
|
("ChargingStations", "evses", "array", "Evses", {"id": "stationId"}),
|
||||||
|
("ChargingStations", "connectors", "array", "Connectors", {"id": "stationId"}),
|
||||||
|
("ChargingStations", "transactions", "array", "Transactions", {"id": "stationId"}),
|
||||||
|
("ChargingStations", "latestStatusNotifications", "array", "LatestStatusNotifications", {"id": "stationId"}),
|
||||||
|
("ChargingStations", "variableAttributes", "array", "VariableAttributes", {"id": "stationPkId"}),
|
||||||
|
|
||||||
|
# Evses
|
||||||
|
("Evses", "connectors", "array", "Connectors", {"evseId": "evseId"}),
|
||||||
|
("Evses", "transactions", "array", "Transactions", {"evseId": "evseId"}),
|
||||||
|
("Evses", "chargingStation", "object", "ChargingStations", {"stationId": "id"}),
|
||||||
|
|
||||||
|
# Connectors
|
||||||
|
("Connectors", "evse", "object", "Evses", {"evseId": "id"}),
|
||||||
|
("Connectors", "chargingStation", "object", "ChargingStations", {"stationId": "id"}),
|
||||||
|
|
||||||
|
# Transactions
|
||||||
|
("Transactions", "chargingStation", "object", "ChargingStations", {"stationId": "id"}),
|
||||||
|
("Transactions", "evse", "object", "Evses", {"evseId": "id"}),
|
||||||
|
("Transactions", "connector", "object", "Connectors", {"connectorId": "id"}),
|
||||||
|
("Transactions", "authorization", "object", "Authorizations", {"authorizationId": "id"}),
|
||||||
|
("Transactions", "location", "object", "Locations", {"locationId": "id"}),
|
||||||
|
|
||||||
|
# LatestStatusNotifications
|
||||||
|
("LatestStatusNotifications", "statusNotification", "object", "StatusNotifications", {"statusNotificationId": "id"}),
|
||||||
|
("LatestStatusNotifications", "chargingStation", "object", "ChargingStations", {"stationId": "id"}),
|
||||||
|
|
||||||
|
# Locations
|
||||||
|
("Locations", "chargingStations", "array", "ChargingStations", {"locationId": "id"}),
|
||||||
|
|
||||||
|
# StatusNotifications
|
||||||
|
("StatusNotifications", "chargingStation", "object", "ChargingStations", {"stationId": "id"}),
|
||||||
|
("StatusNotifications", "evse", "object", "Evses", {"evseId": "id"}),
|
||||||
|
("StatusNotifications", "connector", "object", "Connectors", {"connectorId": "id"}),
|
||||||
|
|
||||||
|
# Authorizations
|
||||||
|
("Authorizations", "transactions", "array", "Transactions", {"authorizationId": "id"}),
|
||||||
|
("Authorizations", "chargingStation", "object", "ChargingStations", {"stationId": "id"}),
|
||||||
|
|
||||||
|
# Tenants
|
||||||
|
("Tenants", "chargingStations", "array", "ChargingStations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "evses", "array", "Evses", {"tenantId": "id"}),
|
||||||
|
("Tenants", "connectors", "array", "Connectors", {"tenantId": "id"}),
|
||||||
|
("Tenants", "transactions", "array", "Transactions", {"tenantId": "id"}),
|
||||||
|
("Tenants", "locations", "array", "Locations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "authorizations", "array", "Authorizations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "tenantPartners", "array", "TenantPartners", {"tenantId": "id"}),
|
||||||
|
("Tenants", "statusNotifications", "array", "StatusNotifications", {"tenantId": "id"}),
|
||||||
|
("Tenants", "latestStatusNotifications", "array", "LatestStatusNotifications", {"tenantId": "id"}),
|
||||||
|
("Tenants", "variableAttributes", "array", "VariableAttributes", {"tenantId": "id"}),
|
||||||
|
("Tenants", "chargingProfiles", "array", "ChargingProfiles", {"tenantId": "id"}),
|
||||||
|
("Tenants", "chargingSchedules", "array", "ChargingSchedules", {"tenantId": "id"}),
|
||||||
|
("Tenants", "salesTariffs", "array", "SalesTariffs", {"tenantId": "id"}),
|
||||||
|
("Tenants", "messageInfos", "array", "MessageInfos", {"tenantId": "id"}),
|
||||||
|
("Tenants", "securityEvents", "array", "SecurityEvents", {"tenantId": "id"}),
|
||||||
|
("Tenants", "certificates", "array", "Certificates", {"tenantId": "id"}),
|
||||||
|
("Tenants", "boots", "array", "Boots", {"tenantId": "id"}),
|
||||||
|
("Tenants", "asyncJobStatuses", "array", "AsyncJobStatuses", {"tenantId": "id"}),
|
||||||
|
("Tenants", "eventData", "array", "EventData", {"tenantId": "id"}),
|
||||||
|
("Tenants", "subscriptions", "array", "Subscriptions", {"tenantId": "id"}),
|
||||||
|
("Tenants", "reservations", "array", "Reservations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "transactionEvents", "array", "TransactionEvents", {"tenantId": "id"}),
|
||||||
|
("Tenants", "startTransactions", "array", "StartTransactions", {"tenantId": "id"}),
|
||||||
|
("Tenants", "stopTransactions", "array", "StopTransactions", {"tenantId": "id"}),
|
||||||
|
("Tenants", "meterValues", "array", "MeterValues", {"tenantId": "id"}),
|
||||||
|
("Tenants", "chargingStationNetworkProfiles", "array", "ChargingStationNetworkProfiles", {"tenantId": "id"}),
|
||||||
|
("Tenants", "serverNetworkProfiles", "array", "ServerNetworkProfiles", {"tenantId": "id"}),
|
||||||
|
("Tenants", "componentVariables", "array", "ComponentVariables", {"tenantId": "id"}),
|
||||||
|
("Tenants", "components", "array", "Components", {"tenantId": "id"}),
|
||||||
|
("Tenants", "variables", "array", "Variables", {"tenantId": "id"}),
|
||||||
|
("Tenants", "evseTypes", "array", "EvseTypes", {"tenantId": "id"}),
|
||||||
|
("Tenants", "compositeSchedules", "array", "CompositeSchedules", {"tenantId": "id"}),
|
||||||
|
("Tenants", "changeConfigurations", "array", "ChangeConfigurations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "chargingStationSequences", "array", "ChargingStationSequences", {"tenantId": "id"}),
|
||||||
|
("Tenants", "chargingStationSecurityInfos", "array", "ChargingStationSecurityInfos", {"tenantId": "id"}),
|
||||||
|
("Tenants", "localListAuthorizations", "array", "LocalListAuthorizations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "localListVersionAuthorizations", "array", "LocalListVersionAuthorizations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "localListVersions", "array", "LocalListVersions", {"tenantId": "id"}),
|
||||||
|
("Tenants", "sendLocalListAuthorizations", "array", "SendLocalListAuthorizations", {"tenantId": "id"}),
|
||||||
|
("Tenants", "sendLocalLists", "array", "SendLocalLists", {"tenantId": "id"}),
|
||||||
|
("Tenants", "deleteCertificateAttempts", "array", "DeleteCertificateAttempts", {"tenantId": "id"}),
|
||||||
|
("Tenants", "installCertificateAttempts", "array", "InstallCertificateAttempts", {"tenantId": "id"}),
|
||||||
|
("Tenants", "installedCertificates", "array", "InstalledCertificates", {"tenantId": "id"}),
|
||||||
|
]
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for table, name, rel_type, remote_table, col_map in relations:
|
||||||
|
print(f"Creating {table}.{name}...", end=" ")
|
||||||
|
if create_relation(table, name, rel_type, remote_table, col_map):
|
||||||
|
print("OK")
|
||||||
|
created += 1
|
||||||
|
else:
|
||||||
|
print("FAIL")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
print(f"\n=== Résultat: {created} créées, {failed} échouées ===")
|
||||||
|
|
||||||
|
# Retest critical relations
|
||||||
|
print("\n=== Vérification finale ===\n")
|
||||||
|
all_ok = True
|
||||||
|
for table, relation, fields in critical_relations:
|
||||||
|
ok, msg = test_relation(table, relation, fields)
|
||||||
|
status = "OK" if ok else "FAIL"
|
||||||
|
print(f" {status}: {table}.{relation}")
|
||||||
|
if not ok:
|
||||||
|
all_ok = False
|
||||||
|
|
||||||
|
if all_ok:
|
||||||
|
print("\n🎉 Toutes les relations fonctionnent !")
|
||||||
|
else:
|
||||||
|
print("\n❌ Certaines relations ne fonctionnent pas encore.")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user