Skip to content

Commit

Permalink
Move to clientApi model (closes #289)
Browse files Browse the repository at this point in the history
  • Loading branch information
SUPERCILEX committed Feb 18, 2020
1 parent e5a9a8b commit 94d932e
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.supercilex.robotscouter.server.functions.emptyTrash
import com.supercilex.robotscouter.server.functions.initUser
import com.supercilex.robotscouter.server.functions.logUserData
import com.supercilex.robotscouter.server.functions.mergeDuplicateTeams
import com.supercilex.robotscouter.server.functions.processClientRequest
import com.supercilex.robotscouter.server.functions.sanitizeDeletionRequest
import com.supercilex.robotscouter.server.functions.transferUserData
import com.supercilex.robotscouter.server.functions.updateDefaultTemplates
Expand All @@ -29,26 +30,33 @@ fun main() {
exports.updateDefaultTemplates = functions.pubsub.topic("update-default-templates")
.onPublish { _, _ -> updateDefaultTemplates() }

val cleanupRuntime = json("timeoutSeconds" to 300, "memory" to "512MB")
exports.emptyTrash = functions.runWith(cleanupRuntime).https
.onCall { data: Array<String>?, context -> emptyTrash(data, context) }
val cleanupRuntime = json("timeoutSeconds" to 540, "memory" to "512MB")
exports.cleanup = functions.runWith(cleanupRuntime).pubsub.topic("daily-tick")
.onPublish { _, _ -> emptyTrash() }
exports.deleteUnusedData = functions.runWith(cleanupRuntime).pubsub.topic("daily-tick")
.onPublish { _, _ -> deleteUnusedData() }
exports.sanitizeDeletionQueue = functions.firestore.document("${deletionQueue.id}/{uid}")
.onWrite { event, _ -> sanitizeDeletionRequest(event) }

val transferRuntime = json("timeoutSeconds" to 540, "memory" to "1GB")
exports.transferUserData = functions.runWith(transferRuntime).https
.onCall { data: Json, context -> transferUserData(data, context) }
exports.updateOwners = functions.runWith(transferRuntime).https
.onCall { data: Json, context -> updateOwners(data, context) }
exports.mergeDuplicateTeams = functions
.runWith(json("timeoutSeconds" to 300, "memory" to "256MB"))
.runWith(json("timeoutSeconds" to 540, "memory" to "1GB"))
.firestore.document("${duplicateTeams.id}/{uid}")
.onWrite { event, _ -> mergeDuplicateTeams(event) }

// TODO remove once we stop getting API requests
exports.emptyTrash = functions.runWith(cleanupRuntime).https
.onCall { data: Array<String>?, context ->
emptyTrash(json("ids" to data), context)
}
exports.transferUserData = functions
.runWith(json("timeoutSeconds" to 540, "memory" to "1GB"))
.https.onCall { data: Json, context -> transferUserData(data, context) }
exports.updateOwners = functions
.runWith(json("timeoutSeconds" to 540, "memory" to "1GB"))
.https.onCall { data: Json, context -> updateOwners(data, context) }

exports.clientApi = functions.runWith(json("timeoutSeconds" to 540, "memory" to "2GB"))
.https.onCall { data: Json, context -> processClientRequest(data, context) }

exports.initUser = functions.auth.user().onCreate { user ->
initUser(user)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.supercilex.robotscouter.server.utils.templates
import com.supercilex.robotscouter.server.utils.toMap
import com.supercilex.robotscouter.server.utils.toTeamString
import com.supercilex.robotscouter.server.utils.toTemplateString
import com.supercilex.robotscouter.server.utils.types.AuthContext
import com.supercilex.robotscouter.server.utils.types.CallableContext
import com.supercilex.robotscouter.server.utils.types.Change
import com.supercilex.robotscouter.server.utils.types.CollectionReference
Expand Down Expand Up @@ -103,8 +104,14 @@ fun emptyTrash(): Promise<*>? = GlobalScope.async {
).processInBatches(10) { processDeletion(it) }
}.asPromise()

fun emptyTrash(data: Array<String>?, context: CallableContext): Promise<*>? {
fun emptyTrash(data: Json, context: CallableContext): Promise<*>? {
val auth = context.auth ?: throw HttpsError("unauthenticated")
return emptyTrash(auth, data)
}

fun emptyTrash(auth: AuthContext, data: Json): Promise<*>? {
@Suppress("UNCHECKED_CAST")
val ids = data["ids"] as? Array<String>?

console.log("Emptying trash for ${auth.uid}.")
return GlobalScope.async {
Expand All @@ -115,7 +122,7 @@ fun emptyTrash(data: Array<String>?, context: CallableContext): Promise<*>? {
return@async
}

processDeletion(requests, data.orEmpty().toList())
processDeletion(requests, ids.orEmpty().toList())
}.asPromise()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.supercilex.robotscouter.server.functions

import com.supercilex.robotscouter.server.utils.types.CallableContext
import com.supercilex.robotscouter.server.utils.types.HttpsError
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asPromise
import kotlinx.coroutines.async
import kotlin.js.Json
import kotlin.js.Promise

fun processClientRequest(data: Json, context: CallableContext): Promise<Any?> {
val auth = context.auth ?: throw HttpsError("unauthenticated")
val rawOperation = data["operation"] as? String
console.log(
"Processing '$rawOperation' operation for user '${auth.uid}' with args: ",
JSON.stringify(data)
)

if (rawOperation == null) {
throw HttpsError("invalid-argument", "An operation must be supplied.")
}
val operation = rawOperation.toUpperCase().replace("-", "_")

return GlobalScope.async {
when (operation) {
"EMPTY_TRASH" -> emptyTrash(auth, data)
"TRANSFER_USER_DATA" -> transferUserData(auth, data)
"UPDATE_OWNERS" -> updateOwners(auth, data)
else -> throw HttpsError("invalid-argument", "Unknown operation: $rawOperation")
}
}.asPromise()
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.supercilex.robotscouter.server.utils.processInBatches
import com.supercilex.robotscouter.server.utils.teams
import com.supercilex.robotscouter.server.utils.toMap
import com.supercilex.robotscouter.server.utils.toTeamString
import com.supercilex.robotscouter.server.utils.types.AuthContext
import com.supercilex.robotscouter.server.utils.types.CallableContext
import com.supercilex.robotscouter.server.utils.types.Change
import com.supercilex.robotscouter.server.utils.types.DeltaDocumentSnapshot
Expand All @@ -52,11 +53,14 @@ import kotlin.js.Promise
import kotlin.js.json

fun transferUserData(data: Json, context: CallableContext): Promise<*>? {
val auth = context.auth
val auth = context.auth ?: throw HttpsError("unauthenticated")
return transferUserData(auth, data)
}

fun transferUserData(auth: AuthContext, data: Json): Promise<*>? {
val token = data[FIRESTORE_TOKEN] as? String
val prevUid = data[FIRESTORE_PREV_UID] as? String

if (auth == null) throw HttpsError("unauthenticated")
if (token == null || prevUid == null) throw HttpsError("invalid-argument")
if (prevUid == auth.uid) {
throw HttpsError("already-exists", "Cannot add and remove the same user")
Expand Down Expand Up @@ -132,12 +136,15 @@ fun transferUserData(data: Json, context: CallableContext): Promise<*>? {
}

fun updateOwners(data: Json, context: CallableContext): Promise<*>? {
val auth = context.auth
val auth = context.auth ?: throw HttpsError("unauthenticated")
return updateOwners(auth, data)
}

fun updateOwners(auth: AuthContext, data: Json): Promise<*>? {
val token = data[FIRESTORE_TOKEN] as? String
val path = data[FIRESTORE_REF] as? String
val prevUid = data[FIRESTORE_PREV_UID]

if (auth == null) throw HttpsError("unauthenticated")
if (token == null || path == null) throw HttpsError("invalid-argument")
if (prevUid != null) {
if (prevUid !is String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ package com.supercilex.robotscouter.server.utils.types
import kotlin.js.Promise

@Suppress("FunctionName", "UNUSED_PARAMETER", "UNUSED_VARIABLE") // Fake class
fun HttpsError(code: String, message: String? = null, details: Any? = null): Nothing {
fun HttpsError(code: String, message: String? = null, details: Any? = null): Throwable {
val functions = functions
js("throw new functions.https.HttpsError(code, message, details)")
throw Exception() // Never going to get called
return js("new functions.https.HttpsError(code, message, details)")
}

external class Https {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ fun cleanup() {
}

fun emptyTrash(ids: List<String>? = null) = Firebase.functions
.getHttpsCallable("emptyTrash")
.call(ids)
.getHttpsCallable("clientApi")
.call(mapOf("operation" to "empty-trash", "ids" to ids))
.logFailures("emptyTrash", ids)
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ suspend fun updateOwner(
newValue: (DocumentReference) -> Any
) = refs.map { ref ->
Firebase.functions
.getHttpsCallable("updateOwners")
.getHttpsCallable("clientApi")
.call(mapOf(
"operation" to "update-owners",
FIRESTORE_TOKEN to token,
FIRESTORE_REF to ref.path,
FIRESTORE_PREV_UID to prevUid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ val userPrefs get() = getUserPrefs(checkNotNull(uid))
val userDeletionQueue get() = deletionQueueRef.document(checkNotNull(uid))

fun transferUserData(prevUid: String, token: String) = Firebase.functions
.getHttpsCallable("transferUserData")
.call(mapOf(FIRESTORE_PREV_UID to prevUid, FIRESTORE_TOKEN to token))
.getHttpsCallable("clientApi")
.call(mapOf(
"operation" to "transfer-user-data",
FIRESTORE_PREV_UID to prevUid,
FIRESTORE_TOKEN to token
))
.logFailures("transferUserData", prevUid, token)

private fun getUserRef(uid: String) = usersRef.document(uid)
Expand Down

0 comments on commit 94d932e

Please sign in to comment.