Skip to content

Commit

Permalink
feat: can create pull requests now :D
Browse files Browse the repository at this point in the history
  • Loading branch information
jacoobes committed Sep 7, 2022
1 parent 44222d2 commit 5fb96a2
Show file tree
Hide file tree
Showing 13 changed files with 312 additions and 108 deletions.
124 changes: 124 additions & 0 deletions .idea/uiDesigner.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 34 additions & 38 deletions src/main/kotlin/Client.kt
Original file line number Diff line number Diff line change
@@ -1,80 +1,76 @@
import io.github.cdimascio.dotenv.dotenv

import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import jdk.nashorn.internal.parser.Token
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import structures.HttpProvider
import structures.api.Response
import structures.api.account.OrgAccount
import structures.api.account.TokenData
import java.util.*
import structures.wrapped.Base
import kotlin.coroutines.CoroutineContext

object Globals {
val eventEmitter = EventEmitter<Base>()
val serializer = Json {
ignoreUnknownKeys = true
prettyPrint = true
explicitNulls = false
}
}

object Client : CoroutineScope {

class Client : CoroutineScope {
val api = HttpProvider(this)
private var parentJob = Job()
val eventEmitter = EventEmitter()
val json = Json { ignoreUnknownKeys = true }
lateinit var application : structures.api.application.Application
lateinit var orgAccount: OrgAccount
lateinit var tokenData : TokenData
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default + parentJob
private val server = embeddedServer(
Netty,
port = 8000,
host = "localhost",
parentCoroutineContext = coroutineContext
) {
configureRouting()
install(ContentNegotiation)
}
init {
dotenv {
systemProperties = true
}
}

inline fun <reified T : Response> on(
inline fun <reified T : Base> on(
eventName: String,
crossinline fn: (T) -> Unit
crossinline fn: suspend (T) -> Unit
) {
launch {
eventEmitter.events.collect {
Globals.eventEmitter.events.collect {
when (eventName) {
"pull_request" -> {
fn(json.decodeFromString(it))
fn(it as T)
}
}
}
}
}
suspend fun loginAsync() {
verifyWithJwt()
verifyWithInstallationApp()
getInstallationToken()

}
private suspend fun verifyWithJwt() {
application = HttpProvider.loginIntoApplicationAsync().await()
}
private suspend fun verifyWithInstallationApp() {
orgAccount = HttpProvider.loginIntoOrgAsync().await()

}
private suspend fun getInstallationToken() {
tokenData = HttpProvider.getInstallationToken(orgAccount).await()
coroutineScope {
withContext(Dispatchers.Default) {
application = api.loginIntoApplicationAsync().await()
orgAccount = api.loginIntoOrgAsync().await()
tokenData = api.getInstallationTokenAsync(orgAccount).await()
}
}
}
/**
* This should start last in order to prevent blocking of thread and listeners
*/
fun startWebhookListener() {
server.start(wait = true)
embeddedServer(
Netty,
port = 8000,
host = "localhost",
parentCoroutineContext = coroutineContext
) {
configureRouting(this@Client)
install(ContentNegotiation) {
json(Globals.serializer)
}
}.start(wait = true).also { println("Started server") }
}
}
8 changes: 5 additions & 3 deletions src/main/kotlin/EventEmitter.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import structures.api.Response
import structures.wrapped.Base

class EventEmitter {
private val _events = MutableSharedFlow<String>() // private mutable shared flow
class EventEmitter<T : Base> {
private val _events = MutableSharedFlow<T>() // private mutable shared flow
val events = _events.asSharedFlow() // publicly exposed as read-only shared flow

suspend fun produceEvent(event: String) {
suspend fun produceEvent(event: T) {
_events.emit(event) // suspends until all subscribers receive it
}
}
46 changes: 34 additions & 12 deletions src/main/kotlin/HttpClientExtensions.kt
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
import HashUtils.verifySignature
import arrow.core.Either
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import java.io.InputStreamReader
import java.io.Reader
import kotlinx.serialization.decodeFromString
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.xor


fun Application.configureRouting() {
fun Application.configureRouting(client: Client) {
routing {
pullRequests()

pullRequests(client)
}

}

fun Routing.pullRequests() {
fun Routing.pullRequests(client: Client) {
post("/pulls") {
val secret = call.request.headers["X-Hub-Signature-256"]!!
val text = call.receiveText()
if(!HashUtils.secureCompare(secret, HashUtils.sha256(text))) {
call.respond(HttpStatusCode.Unauthorized, "Nice try")
val event = call.request.headers["X-Github-Event"]!!
val respText = call.receiveText()
when(val resp = verifySignature(secret, respText)) {
is Either.Left -> {
launch(Dispatchers.Default) {
when(event) {
"pull_request" -> {
Globals.eventEmitter.produceEvent(
structures.wrapped.PullRequestsManager(client,
Globals.serializer.decodeFromString(resp.value)
)
)
}
}
call.respond(HttpStatusCode.OK)
}.join()
}
is Either.Right -> resp.value
}
launch(Dispatchers.Default) {
Client.eventEmitter.produceEvent(text)
}.join()

}
}

Expand Down Expand Up @@ -63,4 +78,11 @@ object HashUtils {
val hash: ByteArray = hasher.doFinal(body.toByteArray())
return "sha256=${hash.toHex()}"
}

suspend inline fun PipelineContext<Unit, ApplicationCall>.verifySignature(secret: String, resp: String) : Either<String, Unit> {
if(secureCompare(secret, sha256(resp))) {
return Either.Left(resp)
}
return Either.Right(call.respond(HttpStatusCode.Unauthorized, "Nice try"))
}
}
23 changes: 14 additions & 9 deletions src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@

import kotlinx.coroutines.*
import structures.api.PullRequests
import structures.api.application.PullRequestAction
import structures.options.PullRequestCreateOptions
import structures.wrapped.PullRequestsManager

fun main() = runBlocking {


Client.loginAsync()
println(Client.orgAccount)
Client.on<PullRequests>("pull_request") { pr_event ->
println(pr_event)
val sernClient = Client()
sernClient.loginAsync()

sernClient.on<PullRequestsManager>("pull_request") { pr_event ->
when(pr_event.action) {
PullRequestAction.Opened -> {
println("new pull request opened")
}
else -> Unit
}
}
Client.startWebhookListener()
sernClient.startWebhookListener()
}




Loading

0 comments on commit 5fb96a2

Please sign in to comment.