Skip to content

Commit 8089e4e

Browse files
authored
Merge pull request #70 from capcom6/issue/61-healthcheck
Add `/health` endpoint
2 parents bfa5776 + 243cbc7 commit 8089e4e

File tree

12 files changed

+232
-15
lines changed

12 files changed

+232
-15
lines changed

app/build.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ dependencies {
6868

6969
implementation("io.ktor:ktor-server-core:$ktor_version")
7070
implementation("io.ktor:ktor-server-netty:$ktor_version")
71-
implementation("io.ktor:ktor-server-cors:$ktor_version")
71+
// implementation("io.ktor:ktor-server-cors:$ktor_version")
7272
implementation("io.ktor:ktor-server-compression:$ktor_version")
73-
implementation "io.ktor:ktor-server-status-pages:$ktor_version"
73+
implementation("io.ktor:ktor-server-status-pages:$ktor_version")
74+
// implementation("io.ktor:ktor-server-default-headers:$ktor_version")
75+
7476

7577
implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
7678
implementation("io.ktor:ktor-serialization-gson:$ktor_version")

app/src/main/java/me/capcom/smsgateway/App.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package me.capcom.smsgateway
22

33
import android.app.Application
4+
import healthModule
45
import me.capcom.smsgateway.data.dbModule
56
import me.capcom.smsgateway.modules.encryption.encryptionModule
67
import me.capcom.smsgateway.modules.gateway.GatewayModule
@@ -29,6 +30,7 @@ class App: Application() {
2930
messagesModule,
3031
encryptionModule,
3132
me.capcom.smsgateway.modules.gateway.gatewayModule,
33+
healthModule,
3234
)
3335
}
3436

app/src/main/java/me/capcom/smsgateway/data/dao/MessageDao.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import me.capcom.smsgateway.data.entities.Message
1010
import me.capcom.smsgateway.data.entities.MessageRecipient
1111
import me.capcom.smsgateway.data.entities.MessageState
1212
import me.capcom.smsgateway.data.entities.MessageWithRecipients
13-
import me.capcom.smsgateway.data.entities.ProcessedStats
13+
import me.capcom.smsgateway.data.entities.MessagesStats
1414
import me.capcom.smsgateway.data.entities.RecipientState
1515

1616
@Dao
1717
interface MessageDao {
1818
@Query("SELECT COUNT(*) as count, MAX(processedAt) as lastTimestamp FROM message WHERE state <> 'Pending' AND state <> 'Failed' AND processedAt >= :timestamp")
19-
fun countProcessedFrom(timestamp: Long): ProcessedStats
19+
fun countProcessedFrom(timestamp: Long): MessagesStats
20+
21+
@Query("SELECT COUNT(*) as count, MAX(createdAt) as lastTimestamp FROM message WHERE state = 'Failed' AND createdAt >= :timestamp")
22+
fun countFailedFrom(timestamp: Long): MessagesStats
2023

2124
@Query("SELECT * FROM message ORDER BY createdAt DESC LIMIT 50")
2225
fun selectLast(): LiveData<List<Message>>

app/src/main/java/me/capcom/smsgateway/data/entities/ProcessedStats.kt renamed to app/src/main/java/me/capcom/smsgateway/data/entities/MessagesStats.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package me.capcom.smsgateway.data.entities
22

3-
data class ProcessedStats(
3+
data class MessagesStats(
44
val count: Int,
55
val lastTimestamp: Long
66
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package me.capcom.smsgateway.modules.health
2+
3+
import me.capcom.smsgateway.modules.health.domain.HealthResult
4+
import me.capcom.smsgateway.modules.health.domain.Status
5+
import me.capcom.smsgateway.modules.messages.MessagesService
6+
7+
class HealthService(
8+
private val messagesSvc: MessagesService,
9+
) {
10+
11+
fun healthCheck(): HealthResult {
12+
val messagesChecks = messagesSvc.healthCheck()
13+
val allChecks = messagesChecks.mapKeys { "messages:${it.key}" }
14+
15+
return HealthResult(
16+
when {
17+
allChecks.values.any { it.status == Status.FAIL } -> Status.FAIL
18+
allChecks.values.any { it.status == Status.WARN } -> Status.WARN
19+
else -> Status.PASS
20+
},
21+
allChecks
22+
)
23+
}
24+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import me.capcom.smsgateway.modules.health.HealthService
2+
import org.koin.dsl.module
3+
4+
val healthModule = module {
5+
single { HealthService(get()) }
6+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package me.capcom.smsgateway.modules.health.domain
2+
3+
data class CheckResult(
4+
val status: Status,
5+
val observedValue: Long,
6+
val observedUnit: String,
7+
val description: String,
8+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package me.capcom.smsgateway.modules.health.domain
2+
3+
data class HealthResult(
4+
val status: Status,
5+
val checks: Map<String, CheckResult>
6+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package me.capcom.smsgateway.modules.health.domain
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
enum class Status {
6+
@SerializedName("pass")
7+
PASS,
8+
9+
@SerializedName("warn")
10+
WARN,
11+
12+
@SerializedName("fail")
13+
FAIL,
14+
}

app/src/main/java/me/capcom/smsgateway/modules/localserver/WebService.kt

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import android.os.PowerManager
99
import androidx.lifecycle.LiveData
1010
import androidx.lifecycle.MutableLiveData
1111
import com.aventrix.jnanoid.jnanoid.NanoIdUtils
12-
import io.ktor.http.HttpHeaders
13-
import io.ktor.http.HttpMethod
1412
import io.ktor.http.HttpStatusCode
13+
import io.ktor.http.toHttpDate
1514
import io.ktor.serialization.gson.gson
1615
import io.ktor.server.application.call
16+
import io.ktor.server.application.createApplicationPlugin
1717
import io.ktor.server.application.install
1818
import io.ktor.server.auth.Authentication
1919
import io.ktor.server.auth.UserIdPrincipal
@@ -22,19 +22,23 @@ import io.ktor.server.auth.basic
2222
import io.ktor.server.engine.embeddedServer
2323
import io.ktor.server.netty.Netty
2424
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
25-
import io.ktor.server.plugins.cors.routing.CORS
2625
import io.ktor.server.plugins.statuspages.StatusPages
2726
import io.ktor.server.request.receive
27+
import io.ktor.server.response.header
2828
import io.ktor.server.response.respond
2929
import io.ktor.server.routing.get
3030
import io.ktor.server.routing.post
3131
import io.ktor.server.routing.route
3232
import io.ktor.server.routing.routing
33+
import io.ktor.util.date.GMTDate
34+
import me.capcom.smsgateway.BuildConfig
3335
import me.capcom.smsgateway.R
3436
import me.capcom.smsgateway.data.entities.Message
3537
import me.capcom.smsgateway.domain.MessageState
3638
import me.capcom.smsgateway.extensions.setDateFormatISO8601
3739
import me.capcom.smsgateway.helpers.SettingsHelper
40+
import me.capcom.smsgateway.modules.health.HealthService
41+
import me.capcom.smsgateway.modules.health.domain.Status
3842
import me.capcom.smsgateway.modules.localserver.domain.Device
3943
import me.capcom.smsgateway.modules.localserver.domain.PostMessageRequest
4044
import me.capcom.smsgateway.modules.localserver.domain.PostMessageResponse
@@ -51,6 +55,7 @@ class WebService : Service() {
5155
private val settingsHelper: SettingsHelper by inject()
5256
private val messagesService: MessagesService by inject()
5357
private val notificationsService: NotificationsService by inject()
58+
private val healthService: HealthService by inject()
5459

5560
private val wakeLock: PowerManager.WakeLock by lazy {
5661
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
@@ -91,13 +96,39 @@ class WebService : Service() {
9196
)
9297
}
9398
}
99+
install(createApplicationPlugin(name = "DateHeader") {
100+
onCall { call ->
101+
call.response.header(
102+
"Date",
103+
GMTDate(null).toHttpDate()
104+
)
105+
}
106+
})
107+
// install(DefaultHeaders) {
108+
// header("Server", "")
109+
// }
110+
// install(CORS) {
111+
// anyHost()
112+
// allowHeader(HttpHeaders.Authorization)
113+
// allowHeader(HttpHeaders.ContentType)
114+
// allowCredentials = true
115+
// }
94116
routing {
95-
install(CORS) {
96-
anyHost()
97-
allowHeader(HttpHeaders.ContentType)
98-
allowHeader(HttpHeaders.Authorization)
99-
allowMethod(HttpMethod.Get)
100-
allowMethod(HttpMethod.Post)
117+
get("/health") {
118+
val healthResult = healthService.healthCheck()
119+
call.respond(
120+
when (healthResult.status) {
121+
Status.FAIL -> HttpStatusCode.InternalServerError
122+
Status.WARN -> HttpStatusCode.OK
123+
Status.PASS -> HttpStatusCode.OK
124+
},
125+
mapOf(
126+
"status" to healthResult.status,
127+
"version" to BuildConfig.VERSION_NAME,
128+
"releaseId" to BuildConfig.VERSION_CODE,
129+
"checks" to healthResult.checks
130+
)
131+
)
101132
}
102133
authenticate("auth-basic") {
103134
get("/") {

0 commit comments

Comments
 (0)