80 lines
3.3 KiB
Kotlin
80 lines
3.3 KiB
Kotlin
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()
|
|
}
|
|
} |