From 5d8fbc6dea5fc98ba032b983b101481c5f1d191b Mon Sep 17 00:00:00 2001 From: Julian Bokelmann Date: Fri, 9 Aug 2024 15:18:14 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix=20firestore=20timestamp=20de?= =?UTF-8?q?coding=20by=20normalizing=20the=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The firestore module was merged with the common module, we try to find a better way --- {firestore => common}/.gitignore | 0 common/build.gradle.kts | 31 ++++- {firestore => common}/proguard-rules.pro | 0 .../src/main/AndroidManifest.xml | 0 .../decoder/StringMapToObjectDecoder.kt | 5 +- .../common}/firestore/DocumentSnapshotExt.kt | 0 .../common}/firestore/QuerySnapshotExt.kt | 7 ++ .../firestore/types/FirestoreTimestampExt.kt | 0 ...estoreTimestampToDecodableTimestampTest.kt | 78 +++++++++++++ firestore/build.gradle.kts | 110 ------------------ ...estoreTimestampToDecodableTimestampTest.kt | 35 ------ 11 files changed, 114 insertions(+), 152 deletions(-) rename {firestore => common}/.gitignore (100%) rename {firestore => common}/proguard-rules.pro (100%) rename {firestore => common}/src/main/AndroidManifest.xml (100%) rename {firestore/src/main/java/de/sipgate/federmappe => common/src/main/kotlin/de/sipgate/federmappe/common}/firestore/DocumentSnapshotExt.kt (100%) rename {firestore/src/main/java/de/sipgate/federmappe => common/src/main/kotlin/de/sipgate/federmappe/common}/firestore/QuerySnapshotExt.kt (84%) rename {firestore/src/main/java/de/sipgate/federmappe => common/src/main/kotlin/de/sipgate/federmappe/common}/firestore/types/FirestoreTimestampExt.kt (100%) create mode 100644 common/src/test/kotlin/de/sipgate/federmappe/common/FirestoreTimestampToDecodableTimestampTest.kt delete mode 100644 firestore/build.gradle.kts delete mode 100644 firestore/src/testDebug/kotlin/FirestoreTimestampToDecodableTimestampTest.kt diff --git a/firestore/.gitignore b/common/.gitignore similarity index 100% rename from firestore/.gitignore rename to common/.gitignore diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 23ee979..fa5d978 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,7 +1,8 @@ import java.util.Properties plugins { - alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.kotlin.serialization) `maven-publish` signing @@ -9,11 +10,33 @@ plugins { version = versionString +android { + namespace = "de.sipgate.federmappe.firestore" + compileSdk = 34 + defaultConfig.minSdk = 23 + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + dependencies { + compileOnly(libs.firebase.firestore) compileOnly(libs.kotlinx.serialization) implementation(libs.kotlinx.datetime) + testImplementation(libs.firebase.firestore) testImplementation(libs.kotlin.test) testImplementation(libs.kotlinx.serialization) testImplementation(libs.mockk.agent) @@ -62,7 +85,7 @@ publishing { } afterEvaluate { - from(components["java"]) + from(components["release"]) } } @@ -78,10 +101,6 @@ publishing { } } -kotlin { - jvmToolchain(8) -} - signing { val signingKey: String? by project val signingPassword: String? by project diff --git a/firestore/proguard-rules.pro b/common/proguard-rules.pro similarity index 100% rename from firestore/proguard-rules.pro rename to common/proguard-rules.pro diff --git a/firestore/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml similarity index 100% rename from firestore/src/main/AndroidManifest.xml rename to common/src/main/AndroidManifest.xml diff --git a/common/src/main/kotlin/de/sipgate/federmappe/common/decoder/StringMapToObjectDecoder.kt b/common/src/main/kotlin/de/sipgate/federmappe/common/decoder/StringMapToObjectDecoder.kt index 15f6d9a..3f4e5ad 100644 --- a/common/src/main/kotlin/de/sipgate/federmappe/common/decoder/StringMapToObjectDecoder.kt +++ b/common/src/main/kotlin/de/sipgate/federmappe/common/decoder/StringMapToObjectDecoder.kt @@ -1,6 +1,8 @@ package de.sipgate.federmappe.common.decoder import de.sipgate.federmappe.common.helper.sortByPrio +import de.sipgate.federmappe.firestore.normalizeStringMap +import de.sipgate.federmappe.firestore.normalizeStringMapNullable import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException import kotlinx.serialization.descriptors.PolymorphicKind @@ -64,7 +66,8 @@ class StringMapToObjectDecoder( return this } - val value = data[key] + val normalizedData = data.normalizeStringMapNullable() + val value = normalizedData[key] val valueDescriptor = descriptor.kind when (valueDescriptor) { diff --git a/firestore/src/main/java/de/sipgate/federmappe/firestore/DocumentSnapshotExt.kt b/common/src/main/kotlin/de/sipgate/federmappe/common/firestore/DocumentSnapshotExt.kt similarity index 100% rename from firestore/src/main/java/de/sipgate/federmappe/firestore/DocumentSnapshotExt.kt rename to common/src/main/kotlin/de/sipgate/federmappe/common/firestore/DocumentSnapshotExt.kt diff --git a/firestore/src/main/java/de/sipgate/federmappe/firestore/QuerySnapshotExt.kt b/common/src/main/kotlin/de/sipgate/federmappe/common/firestore/QuerySnapshotExt.kt similarity index 84% rename from firestore/src/main/java/de/sipgate/federmappe/firestore/QuerySnapshotExt.kt rename to common/src/main/kotlin/de/sipgate/federmappe/common/firestore/QuerySnapshotExt.kt index 8588224..5a4f16d 100644 --- a/firestore/src/main/java/de/sipgate/federmappe/firestore/QuerySnapshotExt.kt +++ b/common/src/main/kotlin/de/sipgate/federmappe/common/firestore/QuerySnapshotExt.kt @@ -29,3 +29,10 @@ fun Map.normalizeStringMap(): Map = mapValues { else -> value } } + +fun Map.normalizeStringMapNullable(): Map = mapValues { + when (val value = it.value) { + is Timestamp -> value.toDecodableTimestamp() + else -> value + } +} diff --git a/firestore/src/main/java/de/sipgate/federmappe/firestore/types/FirestoreTimestampExt.kt b/common/src/main/kotlin/de/sipgate/federmappe/common/firestore/types/FirestoreTimestampExt.kt similarity index 100% rename from firestore/src/main/java/de/sipgate/federmappe/firestore/types/FirestoreTimestampExt.kt rename to common/src/main/kotlin/de/sipgate/federmappe/common/firestore/types/FirestoreTimestampExt.kt diff --git a/common/src/test/kotlin/de/sipgate/federmappe/common/FirestoreTimestampToDecodableTimestampTest.kt b/common/src/test/kotlin/de/sipgate/federmappe/common/FirestoreTimestampToDecodableTimestampTest.kt new file mode 100644 index 0000000..9a61abd --- /dev/null +++ b/common/src/test/kotlin/de/sipgate/federmappe/common/FirestoreTimestampToDecodableTimestampTest.kt @@ -0,0 +1,78 @@ +package de.sipgate.federmappe.common + +import com.google.firebase.Timestamp +import de.sipgate.federmappe.common.decoder.StringMapToObjectDecoder +import de.sipgate.federmappe.firestore.types.toDecodableTimestamp +import kotlinx.datetime.Instant +import kotlinx.datetime.serializers.InstantComponentSerializer +import kotlinx.datetime.toJavaInstant +import kotlinx.serialization.Contextual +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +import kotlinx.serialization.serializer +import java.util.Date +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue + +class FirestoreTimestampToDecodableTimestampTest { + + @Test + fun timestampWithNanosecondPrecisionIsConvertedSuccessfully() { + val expectedInstant = Instant.fromEpochSeconds(1716823455, 854) + + val timestamp = Timestamp(expectedInstant.toJavaInstant()) + + val result = timestamp.toDecodableTimestamp() + assertEquals(expectedInstant.epochSeconds, result["epochSeconds"]) + assertEquals(expectedInstant.nanosecondsOfSecond.toLong(), result["nanosecondsOfSecond"]) + } + + @Test + fun timestampWithSecondPrecisionIsConvertedSuccessfully() { + val expectedDate = Date.from(Instant.fromEpochSeconds(1716823455).toJavaInstant()) + val expectedEpochSeconds = expectedDate.time / 1000 + + val timestamp = Timestamp(expectedDate) + + val result = timestamp.toDecodableTimestamp() + + assertEquals(expectedEpochSeconds, result["epochSeconds"]) + assertEquals(0L, result["nanosecondsOfSecond"]) + } + + @OptIn(ExperimentalSerializationApi::class) + @Test + fun firestoreTimestampIsDecodedCorrectly() { + // Arrange + val expectedInstant = Instant.fromEpochSeconds(1716823455) + val expectedDate = Date.from(expectedInstant.toJavaInstant()) + val timestamp = Timestamp(expectedDate) + + @Serializable + data class MockLocalDataClass( + @Contextual + val createdAt: Instant + ) + + val serializer = serializer() + + val data = mapOf("createdAt" to timestamp) + + // Act + val result = + serializer.deserialize( + StringMapToObjectDecoder( + data, + serializersModule = SerializersModule { contextual(InstantComponentSerializer) } + ), + ) + + // Assert + assertEquals(expectedInstant, result.createdAt) + assertIs(result) + } +} diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts deleted file mode 100644 index 454e6f9..0000000 --- a/firestore/build.gradle.kts +++ /dev/null @@ -1,110 +0,0 @@ -import java.util.Properties - -plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.jetbrains.kotlin.android) - alias(libs.plugins.kotlin.serialization) - `maven-publish` - signing -} - -version = versionString - -android { - namespace = "de.sipgate.federmappe.firestore" - compileSdk = 34 - defaultConfig.minSdk = 23 - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } -} - -dependencies { - api(project(":common")) - - compileOnly(libs.firebase.firestore) - compileOnly(libs.kotlinx.serialization) - - implementation(libs.kotlinx.datetime) - - testImplementation(libs.firebase.firestore) - testImplementation(libs.kotlin.test) - testImplementation(libs.kotlinx.serialization) -} - -tasks.withType().configureEach { - useJUnitPlatform() - - outputs.upToDateWhen { false } -} - -val Project.versionString: String - get() { - val versionProperties = File(project.rootDir, "version.properties") - return versionProperties.inputStream().use { inputStream -> - Properties().run { - load(inputStream) - "${parseInt("majorVersion")}.${parseInt("minorVersion")}.${parseInt("patchVersion")}" - } - } -} - -fun Properties.parseInt(key: String) = (this[key] as String).toInt() - -publishing { - publications.register("release") { - groupId = "de.sipgate" - artifactId = "federmappe-firestore" - version = project.version.toString() - - pom { - name.set("Federmappe") - description.set("") - url.set("https://github.com/sipgate/federmappe") - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - scm { - connection.set("scm:git:git://git@github.com:sipgate/federmappe.git") - developerConnection.set("scm:git:ssh://git@github.com:sipgate/federmappe.git") - url.set("https://github.com/sipgate/federmappe") - } - } - - afterEvaluate { - from(components["release"]) - } - } - - repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/sipgate/federmappe") - credentials { - username = "sipgate" - password = System.getenv("GITHUB_TOKEN") - } - } - } -} - -signing { - val signingKey: String? by project - val signingPassword: String? by project - useInMemoryPgpKeys(signingKey, signingPassword) - sign(publishing.publications) -} diff --git a/firestore/src/testDebug/kotlin/FirestoreTimestampToDecodableTimestampTest.kt b/firestore/src/testDebug/kotlin/FirestoreTimestampToDecodableTimestampTest.kt deleted file mode 100644 index 691fde7..0000000 --- a/firestore/src/testDebug/kotlin/FirestoreTimestampToDecodableTimestampTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package de.sipgate.federmappe.firestore.types - -import com.google.firebase.Timestamp -import kotlinx.datetime.Instant -import kotlinx.datetime.toJavaInstant -import java.util.Date -import kotlin.test.Test -import kotlin.test.assertEquals - -class FirestoreTimestampToDecodableTimestampTest { - - @Test - fun timestampWithNanosecondPrecisionIsConvertedSuccessfully() { - val expectedInstant = Instant.fromEpochSeconds(1716823455, 854) - - val timestamp = Timestamp(expectedInstant.toJavaInstant()) - - val result = timestamp.toDecodableTimestamp() - assertEquals(expectedInstant.epochSeconds, result["epochSeconds"]) - assertEquals(expectedInstant.nanosecondsOfSecond.toLong(), result["nanosecondsOfSecond"]) - } - - @Test - fun timestampWithSecondPrecisionIsConvertedSuccessfully() { - val expectedDate = Date.from(Instant.fromEpochSeconds(1716823455).toJavaInstant()) - val expectedEpochSeconds = expectedDate.time / 1000 - - val timestamp = Timestamp(expectedDate) - - val result = timestamp.toDecodableTimestamp() - - assertEquals(expectedEpochSeconds, result["epochSeconds"]) - assertEquals(0L, result["nanosecondsOfSecond"]) - } -}