Ajout Android CLI
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package iotmonitor
|
||||
|
||||
import java.net.URI
|
||||
import java.net.http.HttpClient
|
||||
import java.net.http.HttpRequest
|
||||
import java.net.http.HttpResponse
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Logique réseau minimale. Réutilisable telle quelle dans une MainActivity.
|
||||
*
|
||||
* Règles de sécurité (modules 03/04) :
|
||||
* - le secret de device passe par en-tête, jamais loggé ;
|
||||
* - on n'imprime que l'identité et le résultat ;
|
||||
* - les erreurs sont remontées sans détail d'implémentation.
|
||||
*/
|
||||
class BackendClient(
|
||||
private val baseUrl: String,
|
||||
private val http: HttpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build(),
|
||||
) {
|
||||
|
||||
/** Envoie une mesure en tant que device. Retourne le code de statut HTTP. */
|
||||
fun ingest(deviceId: String, deviceSecret: String, measure: Measure): Int {
|
||||
val body = buildIngestBody(measure)
|
||||
val request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("$baseUrl/api/ingest"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Device-ID", deviceId)
|
||||
.header("X-Device-Secret", deviceSecret)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(body))
|
||||
.build()
|
||||
|
||||
val response = http.send(request, HttpResponse.BodyHandlers.ofString())
|
||||
// Le secret n'est jamais imprimé.
|
||||
println("[ingest] device=$deviceId -> HTTP ${response.statusCode()}")
|
||||
return response.statusCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les mesures d'un device en tant qu'utilisateur.
|
||||
* L'autorisation est décidée par le backend (module 03) : 403 => refus.
|
||||
*/
|
||||
fun listMeasures(userToken: String, deviceId: String): List<Measure> {
|
||||
val request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("$baseUrl/api/measures?device_id=$deviceId"))
|
||||
.header("Authorization", "Bearer $userToken")
|
||||
.GET()
|
||||
.build()
|
||||
|
||||
val response = http.send(request, HttpResponse.BodyHandlers.ofString())
|
||||
println("[measures] device=$deviceId -> HTTP ${response.statusCode()}")
|
||||
|
||||
if (response.statusCode() != 200) {
|
||||
return emptyList()
|
||||
}
|
||||
return parseMeasures(response.body())
|
||||
}
|
||||
|
||||
private fun buildIngestBody(measure: Measure): String =
|
||||
"""{"device_id":"${measure.device_id}","timestamp":"${measure.timestamp}", "light_value":${measure.light_value}}"""
|
||||
|
||||
private fun parseMeasures(body: String): List<Measure> {
|
||||
// Parse très simple, sans dépendance JSON : on extrait chaque objet
|
||||
// entre accolades (ordre des champs indifférent), puis les valeurs
|
||||
// par clé. Suffisant pour le format minimal du backend.
|
||||
val objectRegex = Regex("""\{[^{}]*\}""")
|
||||
val fieldRegex = Regex(""""([a-z_]+)":"?([^",\}]+)"?""")
|
||||
|
||||
return objectRegex.findAll(body).mapNotNull { objMatch ->
|
||||
val fields = fieldRegex.findAll(objMatch.value)
|
||||
.associate { m -> m.groupValues[1] to m.groupValues[2] }
|
||||
val deviceId = fields["device_id"] ?: return@mapNotNull null
|
||||
val timestamp = fields["timestamp"] ?: return@mapNotNull null
|
||||
val light = fields["light_value"]?.toLongOrNull() ?: return@mapNotNull null
|
||||
Measure(device_id = deviceId, timestamp = timestamp, light_value = light)
|
||||
}.toList()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user