I re-wrote my weather bot completely in Kotlin and split the functionality into three parts:
- The back-end downloads RSS feeds and compares them against the user database, it sends the alerts to user’s mobiles
- The front-end adds users and handles verification
- The front-end administration panel allows for addition, deletion of users.
Both the front-ends are written using jQuery, Bootstrap, vanilla Javascript and Kotlin/ktor. They are both single page web apps and allow for all functions without page reload.
Here is the sign up page on my website:
Here is the admin interface:
The database functions are handled using data classes and SQL objects using a SQL DSL for Kotlin caused Exposed.
Below is an example of the object classes, data classes and a function required to return a list of user objects. This code is used to return a list of users to the admin interface.
The Kotlin SQL DSL makes the programming of very complicated data structures a breeze.
object users : Table() {
val id = integer("id").autoIncrement().primaryKey() // Column
val name = varchar("name", length = 150) // Column
val country = varchar("country", length = 150) // Column
val state = varchar("state", length = 150) // Column
val mobile = varchar("mobile", length = 50) // Column
val opt1 = varchar("opt1", length = 50).nullable() // Column
val opt2 = varchar("opt2", length = 50).nullable() // Column
}
object userkeywords : Table() {
val id = integer("id").autoIncrement().primaryKey() // Column
val uuid = integer("uuid").nullable()
val keywords = varchar("keywords", length = 150) // Column
}
data class completeUser(
val id: Int,
val name: String,
val country: String,
val state: String,
val mobile: String,
val keywords: MutableList,
val opt1: String,
val opt2: String
)
fun getAllUsers(): MutableList {
connectToDB()
var returnedUsers = mutableListOf()
transaction {
SchemaUtils.create(users, userkeywords)
val allKeywords = userkeywords.selectAll()
for (user in users.selectAll()) {
val thisUser = completeUser(
id = user[users.id],
name = user[users.name],
country = user[users.country],
state = user[users.state],
mobile = user[users.mobile],
opt1 = "NIL",
opt2 = "NIL",
keywords = mutableListOf()
)
println("${user[users.id]}: ${user[users.name]}")
for (k in allKeywords) {
if (k[userkeywords.uuid] == user[users.id]) {
val kw = k[userkeywords.keywords]
thisUser.keywords.add(kw)
}
}
returnedUsers.add(thisUser)
}
}
return returnedUsers
}
To send this data in JSON it is very easy.
First “install” the GSON feature:
install(ContentNegotiation) {
gson {
setDateFormat(DateFormat.LONG)
setPrettyPrinting()
}
}
Then add the route:
get("/getUsers") {
var theseUsers = getAllUsers()
call.respond(theseUsers)
}
Pretty braindead stuff really. Handling queries in JavaScript is also very easy (the following function adds all retrieved users to a drop down menu):
var getUserList = function() {
$.ajax({
type: "GET",
url: "/getUsers",
// The key needs to match your method's input parameter (case-sensitive).
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
$('#deleteUser').empty();
data.forEach(function(element) {
$('#deleteUser').append(`
${element.name} mobile: ${element.mobile}`);
});
},
failure: function(errMsg) {
alert(errMsg);
}
});
};
As for the back-end, I had to write the API in Kotlin to connect to the Telstra API. I had to use khttp because ktor client can not set custom headers at the moment.
Here is an example of how it works:
//provision a number
val response = khttp.post(
url = "https://tapi.telstra.com/v2/messages/provisioning/subscriptions",
headers = mapOf("content-type" to "application/json","authorization" to "Bearer ${token}" , "cache-control" to "no-cache"),
data = "{ \"activeDays\": 30 }")
// get/update API token
val payload = mapOf(
"grant_type" to "client_credentials",
"scope" to "NSMS",
"client_id" to "xxx",
"client_secret" to "xxx")
val response = khttp.post(
url = " https://tapi.telstra.com/v2/oauth/token",
headers = mapOf("Content-Type" to "application/x-www-form-urlencoded"),
data = payload)
// send a message
val response = khttp.post(
url = " https://tapi.telstra.com/v2/messages/sms",
headers = mapOf("content-type" to "application/json",
"authorization" to "Bearer ${token}"),
data = ourJson.toString())
I had to setup a dummy route in ktor and use WireShark to sniff the packets to figure out which headers needed to be set before I could finally figure out how the system worked.
Thanks to Telstra for the free 1000 messages per month and thanks to the folks at Jetbrains for all their awesome libraries and products.