All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cohort.CohortApi.kt Maven / Gradle / Ivy

package com.amplitude.experiment.cohort

import com.amplitude.experiment.util.request
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.Base64
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit

/*
 * Based on the Behavioral Cohort API:
 * https://www.docs.developers.amplitude.com/analytics/apis/behavioral-cohorts-api/
 */

internal const val DEFAULT_COHORT_SYNC_URL = "https://cohort.lab.amplitude.com/"

@Serializable
internal data class CohortDescription(
    @SerialName("lastComputed") val lastComputed: Long,
    @SerialName("published") val published: Boolean,
    @SerialName("archived") val archived: Boolean,
    @SerialName("appId") val appId: Int,
    @SerialName("lastMod") val lastMod: Long,
    @SerialName("type") val type: String,
    @SerialName("id") val id: String,
    @SerialName("size") val size: Int,
)

internal object GetCohortsRequest

@Serializable
internal data class GetCohortsResponse(
    @SerialName("cohorts") val cohorts: List,
)

internal data class GetCohortRequest(
    val cohortId: String,
    val lastComputed: Long,
)

@Serializable
internal data class GetCohortResponse(
    @SerialName("cohort") val cohort: CohortDescription,
    @SerialName("user_ids") val userIds: List,
)

internal interface CohortApi {
    fun getCohorts(request: GetCohortsRequest): CompletableFuture
    fun getCohort(request: GetCohortRequest): CompletableFuture
}

internal class CohortApiImpl(
    private val apiKey: String,
    private val secretKey: String,
    private val serverUrl: HttpUrl,
    httpClient: OkHttpClient,
) : CohortApi {

    private val httpClient: OkHttpClient
    private val semaphore = Semaphore(5, true)

    init {
        this.httpClient = httpClient.newBuilder()
            .readTimeout(5, TimeUnit.MINUTES)
            .build()
    }

    override fun getCohorts(request: GetCohortsRequest): CompletableFuture {
        return semaphore.limit {
            get("api/3/cohorts")
        }
    }

    override fun getCohort(request: GetCohortRequest): CompletableFuture {
        return semaphore.limit {
            get("api/3/cohorts/${request.cohortId}", mapOf("lastComputed" to "${request.lastComputed}"))
        }
    }

    private inline fun  get(path: String, queries: Map? = null): CompletableFuture {
        val url = serverUrl.newBuilder().apply {
            addPathSegments(path)
            queries?.forEach {
                addQueryParameter(it.key, it.value)
            }
        }.build()
        val basicAuth = Base64.getEncoder().encodeToString("$apiKey:$secretKey".toByteArray(Charsets.UTF_8))
        val request = Request.Builder()
            .get()
            .url(url)
            .addHeader("Authorization", "Basic $basicAuth")
            .build()
        return httpClient.request(request)
    }
}

private inline fun  Semaphore.limit(block: () -> CompletableFuture): CompletableFuture {
    acquire()
    val result: CompletableFuture = block.invoke().whenComplete { _, _ ->
        release()
    }
    return result
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy