diff --git a/app/src/main/assets/webui/index.html b/app/src/main/assets/webui/index.html
index 6575c90..b15a64e 100644
--- a/app/src/main/assets/webui/index.html
+++ b/app/src/main/assets/webui/index.html
@@ -386,9 +386,8 @@
async function loadStatus() {
try {
const r = await fetch('/api/status', { headers: { 'X-API-Key': apiKey } });
- const d = await r.json();
- const sim = d.sims && d.sims[0];
- document.getElementById('sim-info').textContent = sim
+ const sim = await r.json();
+ document.getElementById('sim-info').textContent = sim.slot !== undefined
? `SIM ${sim.slot + 1} - ${sim.number || sim.carrier || 'Unknown'}`
: 'No SIM';
} catch (e) {}
diff --git a/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt b/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt
index ed4262c..4a69611 100644
--- a/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt
+++ b/app/src/main/java/sh/sar/textpipe/data/db/SmsMessageDao.kt
@@ -33,4 +33,10 @@ interface SmsMessageDao {
@Query("SELECT COUNT(*) FROM sms_messages WHERE type = :type")
suspend fun getCountByType(type: String): Int
+
+ @Query("SELECT * FROM sms_messages WHERE simSlot = :simSlot ORDER BY timestamp DESC")
+ suspend fun getBySimSlot(simSlot: Int): List
+
+ @Query("SELECT * FROM sms_messages WHERE simSlot = :simSlot AND type = :type ORDER BY timestamp DESC")
+ suspend fun getBySimSlotAndType(simSlot: Int, type: String): List
}
diff --git a/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt b/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt
index ac8d828..d773480 100644
--- a/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt
+++ b/app/src/main/java/sh/sar/textpipe/data/repository/SmsRepository.kt
@@ -27,17 +27,20 @@ class SmsRepository(
)
}
- suspend fun getAllMessages(type: String? = null): List {
+ suspend fun getMessagesBySimSlot(simSlot: Int, type: String? = null): List {
val messages = if (type != null) {
- dao.getByType(type)
+ dao.getBySimSlotAndType(simSlot, type)
} else {
- dao.getAll()
+ dao.getBySimSlot(simSlot)
}
return messages.map { it.toResponse() }
}
- suspend fun getMessageById(id: Long): SmsMessageResponse? {
- return dao.getById(id)?.toResponse()
+ suspend fun getMessageById(id: Long, simSlot: Int): SmsMessageResponse? {
+ val message = dao.getById(id) ?: return null
+ // Only return if message belongs to this SIM slot
+ if (message.simSlot != simSlot) return null
+ return message.toResponse()
}
suspend fun insertReceivedSms(from: String, text: String, simSlot: Int): Long {
diff --git a/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt b/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt
index 237ea80..bf7b6dc 100644
--- a/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt
+++ b/app/src/main/java/sh/sar/textpipe/server/TextpipeServer.kt
@@ -153,7 +153,7 @@ class TextpipeServer(
webUIRoutes(context)
// API routes
- statusRoutes(simManager, { startTime }, { currentPort })
+ statusRoutes(simManager)
smsRoutes(smsRepository)
}
}
diff --git a/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt b/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt
index e4e6521..e643325 100644
--- a/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt
+++ b/app/src/main/java/sh/sar/textpipe/server/auth/ApiKeyAuth.kt
@@ -15,15 +15,12 @@ fun Application.configureAuth(simManager: SimManager) {
intercept(ApplicationCallPipeline.Plugins) {
val path = call.request.local.uri
- // Skip auth for status endpoint
- if (path == "/api/status" || path == "/") {
+ // Skip auth for web UI
+ if (path == "/" || path.startsWith("/assets") || !path.startsWith("/api/")) {
return@intercept
}
- // Only require auth for /api/sms/* endpoints
- if (!path.startsWith("/api/sms")) {
- return@intercept
- }
+ // All /api/* endpoints require auth
val apiKey = extractApiKey(call)
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 7e4ec69..f3ac274 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
@@ -9,6 +9,7 @@ import sh.sar.textpipe.data.model.MessagesResponse
import sh.sar.textpipe.data.model.SendSmsRequest
import sh.sar.textpipe.data.repository.SmsRepository
import sh.sar.textpipe.server.auth.ApiKeyAttributeKey
+import sh.sar.textpipe.server.auth.SimSlotAttributeKey
fun Route.smsRoutes(smsRepository: SmsRepository) {
post("/api/sms/send") {
@@ -45,6 +46,12 @@ fun Route.smsRoutes(smsRepository: SmsRepository) {
}
get("/api/sms/messages") {
+ val simSlot = call.attributes.getOrNull(SimSlotAttributeKey)
+ if (simSlot == null) {
+ call.respond(HttpStatusCode.Unauthorized, ErrorResponse("Missing API key"))
+ return@get
+ }
+
val type = call.request.queryParameters["type"]
// Validate type if provided
@@ -53,7 +60,7 @@ fun Route.smsRoutes(smsRepository: SmsRepository) {
return@get
}
- val messages = smsRepository.getAllMessages(type)
+ val messages = smsRepository.getMessagesBySimSlot(simSlot, type)
val response = MessagesResponse(
messages = messages,
total = messages.size
@@ -62,6 +69,12 @@ fun Route.smsRoutes(smsRepository: SmsRepository) {
}
get("/api/sms/status/{id}") {
+ val simSlot = call.attributes.getOrNull(SimSlotAttributeKey)
+ if (simSlot == null) {
+ call.respond(HttpStatusCode.Unauthorized, ErrorResponse("Missing API key"))
+ return@get
+ }
+
val idParam = call.parameters["id"]
val id = idParam?.toLongOrNull()
@@ -70,7 +83,7 @@ fun Route.smsRoutes(smsRepository: SmsRepository) {
return@get
}
- val message = smsRepository.getMessageById(id)
+ val message = smsRepository.getMessageById(id, simSlot)
if (message == null) {
call.respond(HttpStatusCode.NotFound, ErrorResponse("Message not found"))
return@get
diff --git a/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt b/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt
index f367506..983a7a3 100644
--- a/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt
+++ b/app/src/main/java/sh/sar/textpipe/server/routes/StatusRoutes.kt
@@ -1,19 +1,27 @@
package sh.sar.textpipe.server.routes
+import io.ktor.http.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
-import sh.sar.textpipe.data.model.StatusResponse
+import sh.sar.textpipe.data.model.ErrorResponse
+import sh.sar.textpipe.data.model.SimInfo
+import sh.sar.textpipe.server.auth.ApiKeyAttributeKey
import sh.sar.textpipe.sim.SimManager
-fun Route.statusRoutes(simManager: SimManager, getStartTime: () -> Long, getPort: () -> Int) {
+fun Route.statusRoutes(simManager: SimManager) {
get("/api/status") {
- val uptime = System.currentTimeMillis() - getStartTime()
- val response = StatusResponse(
- running = true,
- port = getPort(),
- uptime = uptime,
- sims = simManager.getSimInfoList()
- )
- call.respond(response)
+ val apiKey = call.attributes.getOrNull(ApiKeyAttributeKey)
+ if (apiKey == null) {
+ call.respond(HttpStatusCode.Unauthorized, ErrorResponse("Missing API key"))
+ return@get
+ }
+
+ val simInfo = simManager.getSimInfoForApiKey(apiKey)
+ if (simInfo == null) {
+ call.respond(HttpStatusCode.NotFound, ErrorResponse("SIM not found"))
+ return@get
+ }
+
+ call.respond(simInfo)
}
}
diff --git a/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt b/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt
index d6f4441..bfa5b52 100644
--- a/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt
+++ b/app/src/main/java/sh/sar/textpipe/sim/SimManager.kt
@@ -112,6 +112,16 @@ class SimManager(private val context: Context) {
}
}
+ fun getSimInfoForApiKey(apiKey: String): SimInfo? {
+ val sim = cachedSims.find { it.apiKey == apiKey } ?: return null
+ return SimInfo(
+ slot = sim.slotIndex,
+ carrier = sim.carrierName,
+ number = sim.phoneNumber,
+ hasApiKey = true
+ )
+ }
+
fun getCachedSims(): List = cachedSims
fun regenerateApiKey(slotIndex: Int): String {