Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
295962f
New library for google health api
this-Aditya May 1, 2026
2f64a7b
Include new library as a project sub-module
this-Aditya May 1, 2026
26828e7
Base user model class
this-Aditya May 1, 2026
3c50b79
Google health user data classes added
this-Aditya May 1, 2026
ff84725
Model class for oauth access tokens
this-Aditya May 1, 2026
9a97ecc
User repositories contract added
this-Aditya May 1, 2026
1673b9a
Data classes matching the google subscription endpoint response
this-Aditya May 1, 2026
653c687
Exception classes
this-Aditya May 1, 2026
0c24f3d
Utility classes for avro converters and builders
this-Aditya May 1, 2026
b47c0d9
Base google health avro converter classes to be implemented by all go…
this-Aditya May 1, 2026
540c919
Specific data type converter classes added
this-Aditya May 1, 2026
c5ec008
Simplifying the total calories conversion
this-Aditya May 3, 2026
99213c3
Optimize infering of sleep stage and classic data
this-Aditya Jun 5, 2026
66cc5f1
Abstract method for fetch unauthorized users
this-Aditya Jun 7, 2026
28bf02a
Make serviceUserId nullable, as googlehealth needs to fetch unauthori…
this-Aditya Jun 9, 2026
2b3de1d
Not fetching the unauthorized users and making serviceUserId non null…
this-Aditya Jun 9, 2026
db62814
Update converters as per new google health schemas
this-Aditya Jun 24, 2026
e86e86c
Name builders as per google health conventions
this-Aditya Jun 24, 2026
b981aa7
Remove comment
this-Aditya Jun 24, 2026
dc41909
Add radar publishing plugin from radar commons
this-Aditya May 11, 2026
ab0b5d0
Used radar commons gradle to publish snapshots and releases
this-Aditya May 12, 2026
bfdf797
Add CI workflows to publish snapshots
this-Aditya May 12, 2026
438cd6b
Enable publishing of libraries to maven central
this-Aditya May 12, 2026
30a1f5b
Correct developers field
this-Aditya May 12, 2026
03bbddc
Bump version
this-Aditya Jun 24, 2026
d0588ea
Add converters for parsing ecg and irn data
this-Aditya Jun 25, 2026
f076486
Update exercise converter
this-Aditya Jun 25, 2026
2fb817e
Bump versions
this-Aditya Jun 25, 2026
688a2fd
Handle NaN and send null insead of 0 for absent points
this-Aditya Jun 26, 2026
d0f3b0b
Bump Version
this-Aditya Jun 26, 2026
48bec37
Match google-health-library build to refine-publishing central publis…
this-Aditya Jun 26, 2026
96a18ca
Add desciption of oura library
this-Aditya Jun 26, 2026
2edfa9f
ktlint format
this-Aditya Jun 26, 2026
95efc2e
Updates to pass CI
this-Aditya Jun 27, 2026
6301b49
One more try for avro builders
this-Aditya Jun 27, 2026
2f2fbd4
Bump Schemas
this-Aditya Jun 30, 2026
8056378
Remove oura library
this-Aditya Jul 1, 2026
f6c65c2
Misc changes
this-Aditya Jul 1, 2026
4eb7ead
Merge pull request #200 from RADAR-base/refine-publishing
yatharthranjan Jul 1, 2026
a6ec029
Merge branch 'google-health-lib' into update-converter-schemas
this-Aditya Aug 7, 2026
dc6b601
Remove comment
this-Aditya Jun 24, 2026
04fab1c
Merge branch 'dev' into google-health-lib
this-Aditya Aug 7, 2026
50ad0ee
Merge branch 'google-health-lib' into update-converter-schemas
this-Aditya Aug 7, 2026
3d262aa
Resolve merge conflicts
this-Aditya Aug 7, 2026
a7b8752
Converters for new data types: floors, sedentary period and activity …
this-Aditya Aug 2, 2026
63def30
Misc changes
this-Aditya Aug 11, 2026
6e67e34
Update converter class names
this-Aditya Aug 21, 2026
7a6635d
Make ecg parsing parallel
this-Aditya Aug 21, 2026
c6facd8
Ktlint format fixes
this-Aditya Aug 21, 2026
1832753
Fix HIGH/CRITICAL CVEs in Jackson, Netty, httpcore5, WireMock and Pyt…
pvannierop Sep 23, 2026
23b143b
Use the upcoming shared-library releases and Kotlin 2.3.20
pvannierop Sep 23, 2026
295d576
Bump Jackson, firebase-admin, lz4-java, log4j and urllib3 to fix new …
pvannierop Oct 1, 2026
872a910
Use radar-schemas 0.9.2
pvannierop Oct 6, 2026
01cb723
Bump Gradle to 9.6.0
pvannierop Sep 30, 2026
9c478e7
Write time and utcOffset in Google Health sleep stage and classic rec…
this-Aditya Oct 9, 2026
d73d10d
Write time in Google Health daily resting heart rate and sleep temper…
this-Aditya Oct 9, 2026
dbd362a
Merge pull request #213 from RADAR-base/security/patch-2026-09-23
this-Aditya Oct 12, 2026
be840ce
Add civil times to Google Health ping intervals
this-Aditya Oct 15, 2026
23e5be4
Merge pull request #215 from RADAR-base/googlehealth-time-and-utc-offset
this-Aditya Oct 19, 2026
14c1e8c
Merge remote-tracking branch 'origin/dev' into google-health-lib
this-Aditya Oct 20, 2026
ee013b5
Merge branch 'google-health-lib' into update-converter-schemas
this-Aditya Oct 20, 2026
8771a9d
Merge branch 'update-converter-schemas' into ecg-irn
this-Aditya Oct 20, 2026
8eb169f
Merge branch 'ecg-irn' into gha-new-dt
this-Aditya Oct 20, 2026
f5a3d22
Merge branch 'gha-new-dt' into civil-interval
this-Aditya Oct 20, 2026
68fe522
Merge pull request #201 from RADAR-base/update-converter-schemas
this-Aditya Oct 20, 2026
00a176c
Merge remote-tracking branch 'origin/dev' into ecg-irn
this-Aditya Oct 20, 2026
32bae61
Bump schemas
this-Aditya Oct 20, 2026
3987984
Create google-health-library directory in Docker builds
this-Aditya Oct 20, 2026
3dc4aeb
Merge remote-tracking branch 'origin/ecg-irn' into gha-new-dt
this-Aditya Oct 20, 2026
59a5577
Fix ktlint
this-Aditya Oct 20, 2026
dd045c4
Merge pull request #206 from RADAR-base/gha-new-dt
this-Aditya Oct 20, 2026
05b4320
Merge remote-tracking branch 'origin/ecg-irn' into civil-interval
this-Aditya Oct 20, 2026
9519fb0
Write ECG result classification and HRV frequency powers, derive exer…
this-Aditya Oct 22, 2026
eeaeed4
Bump schemas to 0.9.4
this-Aditya Oct 22, 2026
9091819
Merge pull request #219 from RADAR-base/googlehealth-new-fields
this-Aditya Oct 22, 2026
3f5d29e
Merge pull request #217 from RADAR-base/civil-interval
this-Aditya Oct 22, 2026
02f81f4
Re-run CI after radar-schemas 0.9.4 release
this-Aditya Oct 22, 2026
6206e28
Remove kotlin-test-junit, which conflicts with JUnit 5 in the library…
this-Aditya Oct 23, 2026
a703267
Skip malformed Google Health data points instead of failing the page,…
this-Aditya Oct 23, 2026
f3a4e6c
Add converter robustness tests
this-Aditya Oct 23, 2026
60582a9
Merge pull request #202 from RADAR-base/ecg-irn
this-Aditya Oct 23, 2026
e177e4d
Drop null or non-numeric heart rate and ecg values instead of writing 0
this-Aditya Oct 27, 2026
d2b5f7f
Expose schemas, Jackson and Avro as api dependencies, remove unused o…
this-Aditya Oct 27, 2026
5272dfd
Skip snapshot publishing for a release version instead of failing
this-Aditya Oct 27, 2026
fcc8bd1
Read authorizer users with nullable fields and skip incomplete ones
this-Aditya Oct 27, 2026
65eebf4
Make TransientGoogleHealthException doc independent of its callers
this-Aditya Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/publish-snapshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Publish snapshots

on:
push:
branches: [ dev ]

jobs:
publish:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

# A release version on dev (for example while a release is prepared) skips publishing
# instead of failing the job.
- name: Check SNAPSHOT version
id: version
run: |
if ./gradlew properties --no-daemon -q | grep -q 'version: .*-SNAPSHOT'; then
echo "snapshot=true" >> "$GITHUB_OUTPUT"
else
echo "Not a SNAPSHOT version, skipping snapshot publishing."
echo "snapshot=false" >> "$GITHUB_OUTPUT"
fi

- name: Import GPG signing key
if: steps.version.outputs.snapshot == 'true'
run: |
echo -e "${{ secrets.OSSRH_GPG_SECRET_KEY }}" | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG

- name: Publish to Sonatype snapshots
if: steps.version.outputs.snapshot == 'true'
env:
OSSRH_USER: ${{ secrets.OSSRH_USER_TOKEN_ID }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_USER_TOKEN_SECRET }}
run: >-
./gradlew
-Psigning.gnupg.keyName=${{ secrets.OSSRH_GPG_SECRET_KEY_NAME }}
-Psigning.gnupg.executable=gpg
-Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
publish
39 changes: 39 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,45 @@ jobs:
files: '*/build/libs/*'
repo-token: ${{ secrets.GITHUB_TOKEN }}

publish-jars:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v5

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Assert release (non-SNAPSHOT) version
run: |
if ./gradlew properties --no-daemon -q | grep -q 'version: .*-SNAPSHOT'; then
echo "Refusing to release: project version is still -SNAPSHOT" >&2
exit 1
fi

- name: Import GPG signing key
run: |
echo -e "${{ secrets.OSSRH_GPG_SECRET_KEY }}" | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG

- name: Publish and close staging repository
env:
OSSRH_USER: ${{ secrets.OSSRH_USER_TOKEN_ID }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_USER_TOKEN_SECRET }}
run: >-
./gradlew
-Psigning.gnupg.keyName=${{ secrets.OSSRH_GPG_SECRET_KEY_NAME }}
-Psigning.gnupg.executable=gpg
-Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
publish closeAndReleaseSonatypeStagingRepository

prepare-matrix:
name: Prepare Matrix Output
runs-on: ubuntu-latest
Expand Down
40 changes: 40 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import org.radarbase.gradle.plugin.radarKotlin
import org.radarbase.gradle.plugin.radarPublishing

plugins {
alias(libs.plugins.radar.root.project)
alias(libs.plugins.radar.dependency.management)
alias(libs.plugins.radar.kotlin) apply false
alias(libs.plugins.radar.publishing) apply false
}

repositories {
Expand All @@ -17,9 +19,41 @@ radarRootProject {
gradleVersion.set(libs.versions.gradle)
}

val githubRepoName = "RADAR-base/RADAR-REST-Connector"
val githubProjectUrl = "https://github.com/$githubRepoName"

val publishedSubprojects = setOf("google-health-library")

subprojects {
apply(plugin = "org.radarbase.radar-kotlin")

if (name in publishedSubprojects) {
apply(plugin = "org.radarbase.radar-publishing")
radarPublishing {
githubUrl.set(githubProjectUrl)
developers {
developer {
id.set("yatharthranjan")
name.set("Yatharth Ranjan")
email.set("yatharth.ranjan@kcl.ac.uk")
organization.set("King's College London")
}
developer {
id.set("mpgxvii")
name.set("Pauline Conde")
email.set("mpgxvii@gmail.com")
organization.set("King's College London")
}
developer {
id.set("this-Aditya")
name.set("Aditya Mishra")
email.set("aditya.mishra@kcl.ac.uk")
organization.set("King's College London")
}
}
}
}

// --- Vulnerability fixes start ---
dependencies {
plugins.withType<JavaPlugin> {
Expand All @@ -30,6 +64,12 @@ subprojects {
add("implementation", rootProject.libs.commons.lang3) {
because("Force safe version of commons-lang3 across all modules")
}
add("implementation", rootProject.libs.httpcore5) {
because("CVE-2026-54399: firebase-admin pulls in a vulnerable httpcore5")
}
add("implementation", rootProject.libs.httpcore5.h2) {
because("CVE-2026-54428: firebase-admin pulls in a vulnerable httpcore5-h2")
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions google-health-library/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
description = "Google Health library with utility components to be used in RADAR-Pushendpoint"

dependencies {
implementation libs.kotlin.stdlib

api libs.radar.schemas.commons

implementation libs.jackson.annotations

api libs.jackson.databind

api libs.avro

implementation libs.jackson.datatype.jsr310

testImplementation libs.kotlin.test
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2026 King's College London
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.radarbase.googlehealth.converter

import com.fasterxml.jackson.databind.JsonNode
import org.apache.avro.specific.SpecificRecord
import org.radarbase.googlehealth.user.User
import java.io.IOException

interface AvroConverter {

val topic: String

@Throws(IOException::class)
fun convert(
tree: JsonNode,
user: User,
): List<Pair<SpecificRecord, SpecificRecord>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2026 King's College London
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.radarbase.googlehealth.converter

import com.fasterxml.jackson.databind.JsonNode
import org.apache.avro.specific.SpecificRecord
import org.radarbase.googlehealth.user.User
import org.radarbase.googlehealth.util.googleHealthActivityLevel
import org.radarcns.push.googlehealth.GoogleHealthActivityLevelType

/**
* Converts `activity-level` data points: the activity level the user sustained over an interval,
* one minute long in practice, reported for every interval of the day. The sedentary intervals are
* also reported grouped into longer periods, see [GoogleHealthSedentaryPeriodAvroConverter].
*/
class GoogleHealthActivityLevelAvroConverter(topic: String) : GoogleHealthAvroConverter(topic) {
override fun convertDataPoint(
point: JsonNode,
user: User,
): List<Pair<SpecificRecord, SpecificRecord>> {
val data = point["activityLevel"] ?: return emptyList()
val (start, end) = parseInterval(data) ?: return emptyList()
val record = googleHealthActivityLevel {
time = epochSeconds(start)
timeReceived = nowEpochSeconds()
timeInterval = (end.epochSecond - start.epochSecond).toInt().coerceAtLeast(0)
level = mapLevel(data["activityLevelType"]?.asText())
}
return listOf(user.observationKey to record)
}

/**
* Google's `ACTIVITY_LEVEL_TYPE_UNSPECIFIED` is kept as its own symbol, it means Google itself
* did not classify the interval. Any other symbol, including ones Google adds later, is
* `UNKNOWN`.
*/
private fun mapLevel(text: String?): GoogleHealthActivityLevelType = when (text) {
"SEDENTARY" -> GoogleHealthActivityLevelType.SEDENTARY
"LIGHTLY_ACTIVE" -> GoogleHealthActivityLevelType.LIGHTLY_ACTIVE
"MODERATELY_ACTIVE" -> GoogleHealthActivityLevelType.MODERATELY_ACTIVE
"VERY_ACTIVE" -> GoogleHealthActivityLevelType.VERY_ACTIVE
"ACTIVITY_LEVEL_TYPE_UNSPECIFIED" ->
GoogleHealthActivityLevelType.ACTIVITY_LEVEL_TYPE_UNSPECIFIED
else -> GoogleHealthActivityLevelType.UNKNOWN
}
}
Loading
Loading