feat(health): expose getCharacteristics (DOB / sexe / groupe sanguin)

Nouvelle méthode CoachHealthRoutePlugin.getCharacteristics qui lit
les caractéristiques read-only depuis HealthKit :
- dateOfBirth (ISO8601 date)
- biologicalSex (male/female/other)
- bloodType (A+/A-/B+/B-/AB+/AB-/O+/O-)

Auth étendue : on demande aussi read sur dateOfBirth, biologicalSex
et bloodType en plus de workoutType + workoutRoute.

Utilisé côté web par la page Profil pour auto-remplir les champs
sans saisie manuelle (cf. /api/profile/healthkit-suggestions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-05-13 04:43:30 +00:00
parent 84bf4c0d67
commit e371d0a561

View File

@@ -24,6 +24,7 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin {
CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "requestAuthorization", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getRoute", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getCharacteristics", returnType: CAPPluginReturnPromise),
]
private lazy var store = HKHealthStore()
@@ -42,10 +43,14 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin {
call.resolve(["granted": false, "reason": "HealthKit unavailable"])
return
}
let types: Set<HKObjectType> = [
var types: Set<HKObjectType> = [
HKObjectType.workoutType(),
HKSeriesType.workoutRoute(),
]
// Caractéristiques (DOB, sexe, groupe sanguin) read-only depuis HK
if let dob = HKObjectType.characteristicType(forIdentifier: .dateOfBirth) { types.insert(dob) }
if let sex = HKObjectType.characteristicType(forIdentifier: .biologicalSex) { types.insert(sex) }
if let blood = HKObjectType.characteristicType(forIdentifier: .bloodType) { types.insert(blood) }
store.requestAuthorization(toShare: nil, read: types) { ok, err in
if let err = err {
call.resolve(["granted": false, "reason": err.localizedDescription])
@@ -55,6 +60,43 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin {
}
}
@objc func getCharacteristics(_ call: CAPPluginCall) {
guard HKHealthStore.isHealthDataAvailable() else {
call.resolve(["available": false, "reason": "HealthKit unavailable"])
return
}
var result: [String: Any] = ["available": true]
// Date de naissance
if let dob = try? store.dateOfBirthComponents() {
if let date = Calendar.current.date(from: dob) {
let fmt = ISO8601DateFormatter()
fmt.formatOptions = [.withFullDate]
result["dateOfBirth"] = fmt.string(from: date)
}
}
// Sexe biologique
if let bs = try? store.biologicalSex() {
switch bs.biologicalSex {
case .male: result["biologicalSex"] = "male"
case .female: result["biologicalSex"] = "female"
case .other: result["biologicalSex"] = "other"
default: break // notSet
}
}
// Groupe sanguin
if let bt = try? store.bloodType() {
let map: [HKBloodType: String] = [
.aPositive: "A+", .aNegative: "A-",
.bPositive: "B+", .bNegative: "B-",
.abPositive: "AB+", .abNegative: "AB-",
.oPositive: "O+", .oNegative: "O-",
]
if let s = map[bt.bloodType] { result["bloodType"] = s }
}
call.resolve(result)
}
@objc func getRoute(_ call: CAPPluginCall) {
guard HKHealthStore.isHealthDataAvailable() else {
call.resolve(["available": false, "reason": "HealthKit unavailable", "route": []])