feat: géolocalisation par clé cryptographique (SS58 → Duniter base58)
Au lieu de chercher par nom (title), on résout maintenant par clé :
1. buildIdentityKeyMap() : charge toutes les identités Ğ1v2 depuis Subsquid
avec leur ownerKeyChange → currentSS58 → genesisKey → duniterKey
2. ss58ToDuniterKey() : conversion SS58 v2 (préfixe 2 octets) → base58 Ed25519
= _id Cesium+ (même matériau cryptographique, encodage différent)
3. resolveGeoByKeys() : query Cesium+ par ids{} → résultat exact, pas d'ambiguïté
4. Cache keyMap 10 min : 1 requête Subsquid pour ~8000 identités, pas par refresh
Résultat : les membres migrés v1→v2 avec un profil Cesium+ sont correctement
géolocalisés même si leur nom v2 diffère de leur nom v1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ export interface GeoProfile {
|
||||
}
|
||||
|
||||
const HitSchema = z.object({
|
||||
_id: z.string(),
|
||||
_source: z.object({
|
||||
title: z.string().optional(),
|
||||
city: z.string().optional(),
|
||||
@@ -39,6 +40,47 @@ const SearchResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Résout les coordonnées par clé Duniter (Cesium+ _id).
|
||||
* Plus fiable que par nom car la clé est unique et indépendante de la casse.
|
||||
* Fonctionne pour les membres migrés v1→v2 (clé genesis = _id Cesium+).
|
||||
*/
|
||||
export async function resolveGeoByKeys(
|
||||
duniterKeys: string[]
|
||||
): Promise<Map<string, GeoProfile>> {
|
||||
const unique = [...new Set(duniterKeys.filter(Boolean))];
|
||||
if (unique.length === 0) return new Map();
|
||||
|
||||
const query = {
|
||||
size: unique.length,
|
||||
query: { ids: { values: unique } },
|
||||
_source: ['title', 'city', 'geoPoint'],
|
||||
};
|
||||
|
||||
const response = await fetch(`${CESIUM_ENDPOINT}/user/profile/_search`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(query),
|
||||
});
|
||||
if (!response.ok) throw new Error(`Cesium+ HTTP ${response.status}`);
|
||||
|
||||
const raw = await response.json();
|
||||
const parsed = SearchResponseSchema.parse(raw);
|
||||
|
||||
const result = new Map<string, GeoProfile>();
|
||||
for (const hit of parsed.hits.hits) {
|
||||
const src = hit._source;
|
||||
if (!src.geoPoint) continue;
|
||||
result.set(hit._id, {
|
||||
name: src.title ?? '',
|
||||
city: src.city ?? 'Inconnue',
|
||||
lat: src.geoPoint.lat,
|
||||
lng: src.geoPoint.lon,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout les coordonnées de plusieurs membres Ğ1 par leur nom d'identité.
|
||||
* Envoie une requête Elasticsearch multi-terms en un seul appel.
|
||||
|
||||
Reference in New Issue
Block a user