From c2a0846949b6e5c676ec6456775b59c9e431aac6 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 27 Aug 2026 20:08:02 +0500 Subject: [PATCH] multi-recipient-sms --- .../sh/sar/textpipe/data/model/ApiModels.kt | 48 ++++++++++++++++++- .../sar/textpipe/server/routes/SmsRoutes.kt | 26 ++++++---- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/sh/sar/textpipe/data/model/ApiModels.kt b/app/src/main/java/sh/sar/textpipe/data/model/ApiModels.kt index 6ce9f64..9371153 100644 --- a/app/src/main/java/sh/sar/textpipe/data/model/ApiModels.kt +++ b/app/src/main/java/sh/sar/textpipe/data/model/ApiModels.kt @@ -1,10 +1,56 @@ package sh.sar.textpipe.data.model +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonEncoder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonPrimitive + +object StringOrListSerializer : KSerializer> { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("StringOrList", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): List { + val jsonDecoder = decoder as? JsonDecoder + ?: throw SerializationException("This serializer only supports JSON") + + return when (val element = jsonDecoder.decodeJsonElement()) { + is JsonPrimitive -> listOf(element.content) + is JsonArray -> element.map { it.jsonPrimitive.content } + else -> throw SerializationException( + "'to' must be a phone number string or an array of phone numbers" + ) + } + } + + override fun serialize( + encoder: Encoder, + value: List + ) { + val jsonEncoder = encoder as? JsonEncoder + ?: throw SerializationException("This serializer only supports JSON") + + jsonEncoder.encodeJsonElement( + JsonArray(value.map { JsonPrimitive(it) }) + ) + } +} + + @Serializable data class SendSmsRequest( - val to: String, + @Serializable(with = StringOrListSerializer::class) + val to: List, val text: String ) diff --git a/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt b/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt index f3ac274..ac34223 100644 --- a/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt +++ b/app/src/main/java/sh/sar/textpipe/server/routes/SmsRoutes.kt @@ -27,22 +27,28 @@ fun Route.smsRoutes(smsRepository: SmsRepository) { } // Validate request - if (request.to.isBlank()) { - call.respond(HttpStatusCode.BadRequest, ErrorResponse("'to' field is required")) - return@post - } + if (request.to.isEmpty()) { + call.respond(HttpStatusCode.BadRequest, ErrorResponse("'to' field is required")) + return@post + } if (request.text.isBlank()) { call.respond(HttpStatusCode.BadRequest, ErrorResponse("'text' field is required")) return@post } - val response = smsRepository.sendSms(request.to, request.text, apiKey) - if (response == null) { - call.respond(HttpStatusCode.InternalServerError, ErrorResponse("Failed to send SMS")) - return@post - } + val responses = request.to.mapNotNull { number -> + smsRepository.sendSms(number, request.text, apiKey) + } - call.respond(HttpStatusCode.OK, response) + if (responses.isEmpty()) { + call.respond( + HttpStatusCode.InternalServerError, + ErrorResponse("Failed to send SMS") + ) + return@post + } + + call.respond(HttpStatusCode.OK, responses) } get("/api/sms/messages") {