feat(android): import private Shopee probe tasks
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.json:json:20231013")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnit()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.roubao.task
|
||||
|
||||
private val SHA256_PATTERN = Regex("[0-9a-f]{64}")
|
||||
|
||||
data class ProbeReferenceImage(
|
||||
val relativePath: String,
|
||||
val mediaType: String,
|
||||
val sizeBytes: Long,
|
||||
val sha256: String
|
||||
) {
|
||||
init {
|
||||
require(isSafeRelativePath(relativePath)) {
|
||||
"Reference image path must be a safe relative path"
|
||||
}
|
||||
require(mediaType == "image/jpeg") { "Only JPEG reference images are supported" }
|
||||
require(sizeBytes > 0) { "Reference image size must be positive" }
|
||||
require(SHA256_PATTERN.matches(sha256)) { "Reference image SHA-256 is invalid" }
|
||||
}
|
||||
}
|
||||
|
||||
data class ProbeTask(
|
||||
val schemaVersion: Int = CURRENT_SCHEMA_VERSION,
|
||||
val probeId: String,
|
||||
val sourceOrderNo: String,
|
||||
val sourceStoreName: String,
|
||||
val title: String,
|
||||
val sku: String,
|
||||
val quantity: Int,
|
||||
val referenceImage: ProbeReferenceImage
|
||||
) {
|
||||
init {
|
||||
require(schemaVersion == CURRENT_SCHEMA_VERSION) {
|
||||
"Unsupported ProbeTask schema version"
|
||||
}
|
||||
require(probeId.isNotBlank()) { "Probe task id must not be blank" }
|
||||
require(sourceOrderNo.isNotBlank()) { "Source order number must not be blank" }
|
||||
require(sourceStoreName.isNotBlank()) { "Source store name must not be blank" }
|
||||
require(title.isNotBlank()) { "Title must not be blank" }
|
||||
require(sku.isNotBlank()) { "SKU must not be blank" }
|
||||
require(quantity > 0) { "Quantity must be positive" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CURRENT_SCHEMA_VERSION = 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSafeRelativePath(path: String): Boolean {
|
||||
if (path.isBlank() || path.startsWith("/") || path.startsWith("\\")) {
|
||||
return false
|
||||
}
|
||||
if ('\\' in path) {
|
||||
return false
|
||||
}
|
||||
return path.split('/').none { it.isBlank() || it == "." || it == ".." }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.roubao.task
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
object ProbeTaskJson {
|
||||
fun encode(tasks: Collection<ProbeTask>): String {
|
||||
val root = JSONObject()
|
||||
root.put("schema_version", ProbeTask.CURRENT_SCHEMA_VERSION)
|
||||
root.put("tasks", JSONArray().apply {
|
||||
tasks.forEach { put(it.toJson()) }
|
||||
})
|
||||
return root.toString(2) + "\n"
|
||||
}
|
||||
|
||||
fun decode(payload: String): List<ProbeTask> {
|
||||
val root = JSONObject(payload)
|
||||
require(root.getInt("schema_version") == ProbeTask.CURRENT_SCHEMA_VERSION) {
|
||||
"Unsupported ProbeTask document schema version"
|
||||
}
|
||||
val tasks = root.getJSONArray("tasks")
|
||||
return buildList(tasks.length()) {
|
||||
for (index in 0 until tasks.length()) {
|
||||
add(tasks.getJSONObject(index).toProbeTask())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ProbeTask.toJson(): JSONObject = JSONObject()
|
||||
.put("probe_id", probeId)
|
||||
.put("source_order_no", sourceOrderNo)
|
||||
.put("source_store_name", sourceStoreName)
|
||||
.put("title", title)
|
||||
.put("sku", sku)
|
||||
.put("quantity", quantity)
|
||||
.put(
|
||||
"reference_image",
|
||||
JSONObject()
|
||||
.put("relative_path", referenceImage.relativePath)
|
||||
.put("media_type", referenceImage.mediaType)
|
||||
.put("size_bytes", referenceImage.sizeBytes)
|
||||
.put("sha256", referenceImage.sha256)
|
||||
)
|
||||
|
||||
private fun JSONObject.toProbeTask(): ProbeTask {
|
||||
val image = getJSONObject("reference_image")
|
||||
return ProbeTask(
|
||||
probeId = getString("probe_id"),
|
||||
sourceOrderNo = getString("source_order_no"),
|
||||
sourceStoreName = getString("source_store_name"),
|
||||
title = getString("title"),
|
||||
sku = getString("sku"),
|
||||
quantity = getInt("quantity"),
|
||||
referenceImage = ProbeReferenceImage(
|
||||
relativePath = image.getString("relative_path"),
|
||||
mediaType = image.getString("media_type"),
|
||||
sizeBytes = image.getLong("size_bytes"),
|
||||
sha256 = image.getString("sha256")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.roubao.task
|
||||
|
||||
fun interface TaskSource {
|
||||
suspend fun nextTask(): ProbeTask?
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.roubao.task
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ProbeTaskJsonTest {
|
||||
@Test
|
||||
fun `task document round trips without changing authoritative fields`() {
|
||||
val task = ProbeTask(
|
||||
probeId = "probe_0123456789abcdef",
|
||||
sourceOrderNo = "ORDER_000001",
|
||||
sourceStoreName = "Test Store",
|
||||
title = "Test Product",
|
||||
sku = "SKU-BLACK",
|
||||
quantity = 2,
|
||||
referenceImage = ProbeReferenceImage(
|
||||
relativePath = "assets/0123456789abcdef.jpg",
|
||||
mediaType = "image/jpeg",
|
||||
sizeBytes = 123,
|
||||
sha256 = "a".repeat(64)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(listOf(task), ProbeTaskJson.decode(ProbeTaskJson.encode(listOf(task))))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user