Skip to content

Commit

Permalink
Merge remote-tracking branch 'GitLiveApp/master' into feature/firesto…
Browse files Browse the repository at this point in the history
…re-settings
  • Loading branch information
Daeda88 committed Jan 3, 2024
2 parents c80c5f2 + 3b85f5a commit 2e9cc91
Show file tree
Hide file tree
Showing 14 changed files with 719 additions and 176 deletions.
6 changes: 4 additions & 2 deletions .github/actions/setup_test_action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ runs:
run: chmod +x gradlew
- name: Install Firebase tools
shell: bash
run: npm install -g firebase-tools
run: npm install -g firebase-tools wait-on
- name: Start Firebase emulator
shell: bash
run: "firebase emulators:start --config=./test/firebase.json &"
run: |
firebase emulators:start --config=./test/firebase.json &
wait-on http://127.0.0.1:9099
6 changes: 3 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup test environment
uses: ./.github/actions/setup_test_action
timeout-minutes: 10
- name: Run JS Tests
run: ./gradlew cleanTest jsTest
- name: Upload JS test artifact
Expand All @@ -89,14 +90,13 @@ jobs:
- uses: actions/checkout@v3
- name: Cocoapods cache
uses: actions/cache@v3
id: cocoapods-cache
with:
path: |
~/.cocoapods
~/Library/Caches/CocoaPods
*/build/cocoapods
*/build/classes
key: cocoapods-cache
key: cocoapods-cache-v2
- name: Setup test environment
uses: ./.github/actions/setup_test_action
- name: Run iOS Tests
Expand All @@ -112,4 +112,4 @@ jobs:
if: failure()
with:
name: "Firebase Debug Log"
path: "**/firebase-debug.log"
path: "**/firebase-debug.log"
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Firebase as a backend for <a href="https://www.jetbrains.com/lp/compose-multipla

The following libraries are available for the various Firebase products.

| Service or Product | Gradle Dependency | API Coverage |
|---------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Service or Product | Gradle Dependency | API Coverage |
|---------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.10.4/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) |
| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.10.4/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) |
| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.10.4/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) |
Expand Down Expand Up @@ -178,18 +178,36 @@ user.updateProfile(profileUpdates)
user.updateProfile(displayName = "state", photoURL = "CA")
```

<h3><a href="https://kotlinlang.org/docs/reference/functions.html#named-arguments">Named arguments</a></h3>

To improve readability functions such as the Cloud Firestore query operators use named arguments:

<h3><a href="https://kotlinlang.org/docs/functions.html#infix-notation">Infix notation</a></h3>

To improve readability and reduce boilerplate for functions such as the Cloud Firestore query operators are built with infix notation:

```kotlin
citiesRef.whereEqualTo("state", "CA")
citiesRef.whereArrayContains("regions", "west_coast")
citiesRef.where(Filter.and(
Filter.equalTo("state", "CA"),
Filter.or(
Filter.equalTo("capital", true),
Filter.greaterThanOrEqualTo("population", 1000000)
)
))

//...becomes...

citiesRef.where("state", equalTo = "CA")
citiesRef.where("regions", arrayContains = "west_coast")
citiesRef.where { "state" equalTo "CA" }
citiesRef.where { "regions" contains "west_coast" }
citiesRef.where {
all(
"state" equalTo "CA",
any(
"capital" equalTo true,
"population" greaterThanOrEqualTo 1000000
)
)
}
```

<h3><a href="https://kotlinlang.org/docs/reference/operator-overloading.html">Operator overloading</a></h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import kotlinx.serialization.SerializationStrategy
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor

import com.google.firebase.firestore.Query as AndroidQuery
import com.google.firebase.firestore.FieldPath as AndroidFieldPath
import com.google.firebase.firestore.Filter as AndroidFilter

actual val Firebase.firestore get() =
FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance())

Expand Down Expand Up @@ -342,7 +346,7 @@ actual class DocumentReference actual constructor(internal actual val nativeValu
}
}

actual open class Query(open val android: com.google.firebase.firestore.Query) {
actual open class Query(open val android: AndroidQuery) {

actual suspend fun get() = QuerySnapshot(android.get().await())

Expand All @@ -358,39 +362,72 @@ actual open class Query(open val android: com.google.firebase.firestore.Query) {
exception?.let { close(exception) }
}

internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo))
internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(android.whereEqualTo(path.android, equalTo))

internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, equalTo.android))
internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, equalTo.android))

internal actual fun _where(field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = Query(
(lessThan?.let { android.whereLessThan(field, it) } ?: android).let { android2 ->
(greaterThan?.let { android2.whereGreaterThan(field, it) } ?: android2).let { android3 ->
arrayContains?.let { android3.whereArrayContains(field, it) } ?: android3
}
}
internal actual fun where(filter: Filter) = Query(
android.where(filter.toAndroidFilter())
)

internal actual fun _where(path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = Query(
(lessThan?.let { android.whereLessThan(path.android, it) } ?: android).let { android2 ->
(greaterThan?.let { android2.whereGreaterThan(path.android, it) } ?: android2).let { android3 ->
arrayContains?.let { android3.whereArrayContains(path.android, it) } ?: android3
private fun Filter.toAndroidFilter(): AndroidFilter = when (this) {
is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray())
is Filter.Or -> AndroidFilter.or(*filters.map { it.toAndroidFilter() }.toTypedArray())
is Filter.Field -> {
when (constraint) {
is WhereConstraint.ForNullableObject -> {
val modifier: (String, Any?) -> AndroidFilter = when (constraint) {
is WhereConstraint.EqualTo -> AndroidFilter::equalTo
is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo
}
modifier.invoke(field, constraint.safeValue)
}
is WhereConstraint.ForObject -> {
val modifier: (String, Any) -> AndroidFilter = when (constraint) {
is WhereConstraint.LessThan -> AndroidFilter::lessThan
is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan
is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo
is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo
is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains
}
modifier.invoke(field, constraint.safeValue)
}
is WhereConstraint.ForArray -> {
val modifier: (String, List<Any>) -> AndroidFilter = when (constraint) {
is WhereConstraint.InArray -> AndroidFilter::inArray
is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny
is WhereConstraint.NotInArray -> AndroidFilter::notInArray
}
modifier.invoke(field, constraint.safeValues)
}
}
}
)

internal actual fun _where(field: String, inArray: List<Any>?, arrayContainsAny: List<Any>?) = Query(
(inArray?.let { android.whereIn(field, it) } ?: android).let { android2 ->
arrayContainsAny?.let { android2.whereArrayContainsAny(field, it) } ?: android2
}
)

internal actual fun _where(path: FieldPath, inArray: List<Any>?, arrayContainsAny: List<Any>?) = Query(
(inArray?.let { android.whereIn(path.android, it) } ?: android).let { android2 ->
arrayContainsAny?.let { android2.whereArrayContainsAny(path.android, it) } ?: android2
is Filter.Path -> {
when (constraint) {
is WhereConstraint.ForNullableObject -> {
val modifier: (AndroidFieldPath, Any?) -> AndroidFilter = when (constraint) {
is WhereConstraint.EqualTo -> AndroidFilter::equalTo
is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo
}
modifier.invoke(path.android, constraint.safeValue)
}
is WhereConstraint.ForObject -> {
val modifier: (AndroidFieldPath, Any) -> AndroidFilter = when (constraint) {
is WhereConstraint.LessThan -> AndroidFilter::lessThan
is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan
is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo
is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo
is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains
}
modifier.invoke(path.android, constraint.safeValue)
}
is WhereConstraint.ForArray -> {
val modifier: (AndroidFieldPath, List<Any>) -> AndroidFilter = when (constraint) {
is WhereConstraint.InArray -> AndroidFilter::inArray
is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny
is WhereConstraint.NotInArray -> AndroidFilter::notInArray
}
modifier.invoke(path.android, constraint.safeValues)
}
}
}
)
}

internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction))
internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package dev.gitlive.firebase.firestore

sealed interface WhereConstraint {

sealed interface ForNullableObject : WhereConstraint {
val value: Any?
val safeValue get() = value?.safeValue
}

sealed interface ForObject : WhereConstraint {
val value: Any
val safeValue get() = value.safeValue
}
sealed interface ForArray : WhereConstraint {
val values: List<Any>
val safeValues get() = values.map { it.safeValue }
}

data class EqualTo internal constructor(override val value: Any?) : ForNullableObject
data class NotEqualTo internal constructor(override val value: Any?) : ForNullableObject
data class LessThan internal constructor(override val value: Any) : ForObject
data class GreaterThan internal constructor(override val value: Any) : ForObject
data class LessThanOrEqualTo internal constructor(override val value: Any) : ForObject
data class GreaterThanOrEqualTo internal constructor(override val value: Any) : ForObject
data class ArrayContains internal constructor(override val value: Any) : ForObject
data class ArrayContainsAny internal constructor(override val values: List<Any>) : ForArray
data class InArray internal constructor(override val values: List<Any>) : ForArray
data class NotInArray internal constructor(override val values: List<Any>) : ForArray
}

sealed class Filter {
data class And internal constructor(val filters: List<Filter>) : Filter()
data class Or internal constructor(val filters: List<Filter>) : Filter()
sealed class WithConstraint : Filter() {
abstract val constraint: WhereConstraint
}

data class Field internal constructor(val field: String, override val constraint: WhereConstraint) : WithConstraint()
data class Path internal constructor(val path: FieldPath, override val constraint: WhereConstraint) : WithConstraint()
}

class FilterBuilder internal constructor() {

infix fun String.equalTo(value: Any?): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.EqualTo(value))
}

infix fun FieldPath.equalTo(value: Any?): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.EqualTo(value))
}

infix fun String.notEqualTo(value: Any?): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.NotEqualTo(value))
}

infix fun FieldPath.notEqualTo(value: Any?): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.NotEqualTo(value))
}

infix fun String.lessThan(value: Any): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.LessThan(value))
}

infix fun FieldPath.lessThan(value: Any): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.LessThan(value))
}

infix fun String.greaterThan(value: Any): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.GreaterThan(value))
}

infix fun FieldPath.greaterThan(value: Any): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.GreaterThan(value))
}

infix fun String.lessThanOrEqualTo(value: Any): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.LessThanOrEqualTo(value))
}

infix fun FieldPath.lessThanOrEqualTo(value: Any): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.LessThanOrEqualTo(value))
}

infix fun String.greaterThanOrEqualTo(value: Any): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.GreaterThanOrEqualTo(value))
}

infix fun FieldPath.greaterThanOrEqualTo(value: Any): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.GreaterThanOrEqualTo(value))
}

infix fun String.contains(value: Any): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.ArrayContains(value))
}

infix fun FieldPath.contains(value: Any): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.ArrayContains(value))
}

infix fun String.containsAny(values: List<Any>): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.ArrayContainsAny(values))
}

infix fun FieldPath.containsAny(values: List<Any>): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.ArrayContainsAny(values))
}

infix fun String.inArray(values: List<Any>): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.InArray(values))
}

infix fun FieldPath.inArray(values: List<Any>): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.InArray(values))
}

infix fun String.notInArray(values: List<Any>): Filter.WithConstraint {
return Filter.Field(this, WhereConstraint.NotInArray(values))
}

infix fun FieldPath.notInArray(values: List<Any>): Filter.WithConstraint {
return Filter.Path(this, WhereConstraint.NotInArray(values))
}

infix fun Filter.and(right: Filter): Filter.And {
val leftList = when (this) {
is Filter.And -> filters
else -> listOf(this)
}
val rightList = when (right) {
is Filter.And -> right.filters
else -> listOf(right)
}
return Filter.And(leftList + rightList)
}

infix fun Filter.or(right: Filter): Filter.Or {
val leftList = when (this) {
is Filter.Or -> filters
else -> listOf(this)
}
val rightList = when (right) {
is Filter.Or -> right.filters
else -> listOf(right)
}
return Filter.Or(leftList + rightList)
}

fun all(vararg filters: Filter): Filter? = filters.toList().combine { left, right -> left and right }
fun any(vararg filters: Filter): Filter? = filters.toList().combine { left, right -> left or right }

private fun Collection<Filter>.combine(over: (Filter, Filter) -> Filter): Filter? = fold<Filter, Filter?>(null) { acc, filter ->
acc?.let { over(acc, filter) } ?: filter
}
}
Loading

0 comments on commit 2e9cc91

Please sign in to comment.