stioner.plugin.2024.9.1.source-code.PublishQuestions.kt Maven / Gradle / Ivy
@file:Suppress("InvalidPackageDeclaration")
package edu.illinois.cs.cs125.questioner.plugin
import edu.illinois.cs.cs125.questioner.lib.Question
import edu.illinois.cs.cs125.questioner.lib.loadQuestionList
import edu.illinois.cs.cs125.questioner.lib.toJSON
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import java.io.ByteArrayOutputStream
import java.io.File
import java.net.HttpURLConnection
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.function.BiPredicate
import java.util.zip.GZIPOutputStream
abstract class PublishQuestions : DefaultTask() {
@Internal
lateinit var endpoint: QuestionerConfig.EndPoint
@InputFile
val inputFile: File = project.layout.buildDirectory.dir("questioner/questions.json").get().asFile
@Internal
lateinit var ignorePackages: List
@Internal
lateinit var publishIncludes: BiPredicate
init {
group = "Publish"
description = "Publish questions to a configured endpoint."
}
@TaskAction
fun publish() {
val uri = URI(endpoint.url)
require(uri.scheme == "http" || uri.scheme == "https") { "Invalid destination scheme: ${uri.scheme}" }
val allQuestions = inputFile.loadQuestionList().filter { question ->
!ignorePackages.any { prefix -> question.published.packageName.startsWith(prefix) }
}
val questions = allQuestions.filter { question ->
question.metadata?.publish != false
}.filter { question ->
publishIncludes.test(endpoint, question)
}
require(questions.isNotEmpty()) {
val ignoredQuestions = allQuestions.filter { question -> question.metadata?.publish == false }
val unpublishedQuestions = ignoredQuestions.filter { question -> !publishIncludes.test(endpoint, question) }
"No questions to publish: ${
if (ignorePackages.isNotEmpty()) {
"disabled packages ${ignorePackages.joinToString(",")}"
} else {
""
}
}${
if (ignoredQuestions.isNotEmpty()) {
", ignored questions ${
ignoredQuestions.joinToString(",") { question -> question.published.path }
}"
} else {
""
}
}${
if (unpublishedQuestions.isNotEmpty()) {
", unpublished questions ${
unpublishedQuestions.joinToString(",") { question -> question.published.path }
}"
} else {
""
}
}"
}
require(questions.all { question -> question.validated }) { "Cannot publish until all questions are validated" }
questions.forEach { question -> question.cleanForUpload() }
HttpRequest.newBuilder()
.uri(uri)
.header("Authorization", """Bearer ${endpoint.token}""")
.header("Content-Type", "application/json")
.header("Content-Encoding", "gzip")
.POST(HttpRequest.BodyPublishers.ofByteArray(questions.toJSON().gzip()))
.build().let { httpRequest ->
HttpClient.newHttpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString())
}.also { httpResponse ->
check(httpResponse.statusCode() == HttpURLConnection.HTTP_OK) {
val message = try {
httpResponse.body()
} catch (e: Exception) {
null
}
"Upload request to ${endpoint.url} failed: ${httpResponse.statusCode()}${
if (message != null) {
"\n$message"
} else {
""
}
}"
}
}
}
}
internal fun String.gzip(): ByteArray? {
check(this.isNotEmpty())
val obj = ByteArrayOutputStream()
GZIPOutputStream(obj).apply {
write(toByteArray())
close()
}
return obj.toByteArray()
}