Files
AAS_NewGeneration/src/web/WebApp.kt
2026-02-09 17:03:50 +07:00

2848 lines
173 KiB
Kotlin

package web
import StreamerOutputs
import barix.BarixConnection
import codes.Somecodes
import codes.Somecodes.Companion.GetAppUpTime
import codes.Somecodes.Companion.GetSensorsInfo
import codes.Somecodes.Companion.GetUptime
import codes.Somecodes.Companion.ListAudioFiles
import codes.Somecodes.Companion.String_To_List
import codes.Somecodes.Companion.ValiDateForLogHtml
import codes.Somecodes.Companion.ValidDirectory
import codes.Somecodes.Companion.ValidFile
import codes.Somecodes.Companion.ValidIPV4
import codes.Somecodes.Companion.ValidScheduleDay
import codes.Somecodes.Companion.ValidScheduleTime
import codes.Somecodes.Companion.ValidString
import codes.Somecodes.Companion.ValidStrings
import codes.Somecodes.Companion.datetimeformat1
import codes.configFile
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import content.Category
import content.Language
import content.ScheduleDay
import content.VoiceType
import database.data.BroadcastZones
import database.data.LanguageLink
import database.MariaDB
import database.data.Messagebank
import database.data.ScheduleBank
import database.data.SoundChannel
import database.data.Soundbank
import database.data.UserDB
import db
import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.*
import io.javalin.http.Context
import io.javalin.json.JavalinJackson
import io.javalin.websocket.WsMessageContext
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.nio.file.Files
import java.time.LocalDateTime
import codes.configKeys
import config
import database.data.LogSemiauto
import database.data.QueueTable
import google.GoogleTTS
import google.autoadd
import google.fileoperation
import io.javalin.websocket.WsCloseStatus
import org.tinylog.Logger
import version
import java.io.File
import java.nio.ByteBuffer
import java.nio.file.Path
import java.util.UUID
@Suppress("unused")
class WebApp(val listenPort: Int, var userlist: List<Pair<String, String>>, val _config: configFile) {
lateinit var app: Javalin
lateinit var semiauto: Javalin
val objectmapper = jacksonObjectMapper()
val WsContextMap = mutableMapOf<String, LiveListenData>()
val ttsjob = GoogleTTS()
private fun SendReply(context: WsMessageContext, command: String, value: String) {
try {
if (context.session.isOpen) {
context.send(objectmapper.writeValueAsString(WebsocketReply(command, value)))
}
} catch (_: Exception) {
}
}
fun Start() {
Start_WebServer()
Start_SemiAutoServer()
}
private fun Start_WebServer() {
Logger.info { "Starting Web Application on port $listenPort" }
app = Javalin.create { config ->
config.useVirtualThreads = true
config.staticFiles.add("/webpage")
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
config.router.apiBuilder {
before {
//Logger.info {"${it.ip()} connecting ${it.method()} to ${it.path()}"}
}
path("/") {
get { ctx ->
// Serve the main page
ctx.cookie("aas-user", "")
//ctx.sessionAttribute("user", null) // Clear user session
ctx.redirect("login.html")
}
}
path("logout") {
get { ctx ->
ctx.cookie("aas-user", "")
//ctx.sessionAttribute<String>("user", null) // Clear user session
ctx.redirect("login.html")
}
}
path("login.html") {
post { it ->
// get username and password from form
val username = it.formParam("username")
val password = it.formParam("password")
if (username == null || password == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Username and password are required")))
return@post
}
// Check if user exists in userlist
val user = userlist.find { it.first == username && it.second == password }
if (user == null) {
it.status(401)
.result(objectmapper.writeValueAsString(resultMessage("Invalid username or password")))
return@post
}
// Set user session
//it.sessionAttribute("user", user.first)
it.cookie("aas-user", user.first)
// Redirect to home page
if (user.first == GetAdminUserFromConfig()) {
it.redirect("homeadmin.html")
} else {
it.redirect("homeviewer.html")
}
}
}
// home for admin user
path("homeadmin.html") {
before { CheckUsers(it) }
ws("/ws") { ws ->
// WebSocket endpoint for home
ws.onConnect {
it.enableAutomaticPings()
}
ws.onMessage { wsMessageContext ->
try {
val cmd =
objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
when (cmd.command) {
"getAppVersion" -> {
SendReply(wsMessageContext, cmd.command, version)
}
"getSystemTime" -> {
val systemtime = LocalDateTime.now().format(datetimeformat1)
val uptime = GetUptime()
val apptime = GetAppUpTime()
// create JSON object of systemtime, uptime and apptime
val timejson = objectmapper.writeValueAsString(
mapOf(
"systemtime" to systemtime,
"uptime" to uptime,
"apptime" to apptime
)
)
SendReply(
wsMessageContext,
cmd.command,
timejson
)
}
"getCPUStatus" -> {
Somecodes.getCPUUsage { vv ->
val sv = GetSensorsInfo()
if (sv.isNotEmpty()) {
SendReply(wsMessageContext, cmd.command, vv + "\n" + sv)
} else {
SendReply(wsMessageContext, cmd.command, vv)
}
}
}
"getMemoryStatus" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(Somecodes.getMemoryUsage())
)
}
"getDiskStatus" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(Somecodes.getDiskUsage())
)
}
"getNetworkStatus" -> {
Somecodes.GetNetworkStatus { nn ->
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(nn)
)
}
}
"getPagingQueue" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(db.queuepagingDB.List)
)
}
"getAASQueue" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(db.queuetableDB.List)
)
}
"getStreamerOutputs" -> {
val reply: List<StreamerOutputData> =
StreamerOutputs.map { so -> StreamerOutputData.fromBarixConnection(so.value) }
SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(reply))
}
"start_generate_tts" -> {
val js: JsonNode = objectmapper.readTree(cmd.data)
SendReply(wsMessageContext, cmd.command, "ok")
val voicetype = js.get("voicetype")?.asText("Wavenet-A") ?: "Wavenet-A"
val languagecode = js.get("languagecode")?.asText("id-ID") ?: "id-ID"
val databasesource = js.get("databasesource")?.asText("VOICE_1") ?: "VOICE_1"
val targetas = js.get("targetas")?.asText("VOICE_2") ?: "VOICE_2"
val fop = js.get("fileoperation")?.asText("overwrite") ?: "overwrite"
val aa = js.get("autoadd")?.asText("add") ?: "add"
Logger.info { "Starting TTS Soundbank Generation, VoiceType=$voicetype, Language" }
ttsjob.GenerateSoundbank(
voicetype, Language.from_GoogleTTSLanguage(languagecode)!!,
VoiceType.fromString(databasesource),
VoiceType.fromString(targetas), fileoperation.fromString(fop),
autoadd.fromString(aa)
) { progress, message ->
SendReply(
wsMessageContext,
"tts_generate_progress",
objectmapper.writeValueAsString(
mapOf(
"progress" to progress,
"message" to message
)
)
)
}
}
"stop_generate_tts" -> {
SendReply(wsMessageContext, cmd.command, "ok")
ttsjob.StopGenerate()
}
else -> {
SendReply(wsMessageContext, cmd.command, "Unknown command")
}
}
} catch (e: Exception) {
if (e.message != null && (e.message is String) && e.message!!.isNotEmpty()) {
Logger.error { "Error processing WebSocket message: ${e.message}" }
}
}
}
}
}
// home for viewer user
path("homeviewer.html") {
before { CheckUsers(it) }
ws("/ws") { ws ->
// WebSocket endpoint for home
ws.onConnect {
it.enableAutomaticPings()
}
ws.onMessage { wsMessageContext ->
try {
val cmd =
objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
when (cmd.command) {
"getSystemTime" -> {
val systemtime = LocalDateTime.now().format(datetimeformat1)
val uptime = GetUptime()
val apptime = GetAppUpTime()
// create JSON object of systemtime, uptime and apptime
val timejson = objectmapper.writeValueAsString(
mapOf(
"systemtime" to systemtime,
"uptime" to uptime,
"apptime" to apptime
)
)
SendReply(
wsMessageContext,
cmd.command,
timejson
)
}
"getCPUStatus" -> {
Somecodes.getCPUUsage { vv ->
val sv = GetSensorsInfo()
if (sv.isNotEmpty()) {
SendReply(wsMessageContext, cmd.command, vv + "\n" + sv)
} else {
SendReply(wsMessageContext, cmd.command, vv)
}
}
}
"getMemoryStatus" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(Somecodes.getMemoryUsage())
)
}
"getDiskStatus" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(Somecodes.getDiskUsage())
)
}
"getNetworkStatus" -> {
Somecodes.GetNetworkStatus { nn ->
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(nn)
)
}
}
"getPagingQueue" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(db.queuepagingDB.List)
)
}
"getAASQueue" -> {
SendReply(
wsMessageContext,
cmd.command,
objectmapper.writeValueAsString(db.queuetableDB.List)
)
}
"getStreamerOutputs" -> {
val reply: List<StreamerOutputData> =
StreamerOutputs.map { so -> StreamerOutputData.fromBarixConnection(so.value) }
SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(reply))
}
else -> {
SendReply(wsMessageContext, cmd.command, "Unknown command")
}
}
} catch (e: Exception) {
if (e.message != null && (e.message is String) && e.message!!.isNotEmpty()) {
Logger.error { "Error processing WebSocket message: ${e.message}" }
}
}
}
}
}
path("soundbank.html") {
before {
val user = CheckUsers(it)
// only admin user can access soundbank page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("messagebank.html") {
before {
val user = CheckUsers(it)
// only admin user can access messagebank page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("language.html") {
before {
val user = CheckUsers(it)
// only admin user can access language link page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("log.html") {
before { CheckUsers(it) }
}
path("setting.html") {
before {
val user = CheckUsers(it)
// only admin user can access settings page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("timer.html") {
before {
val user = CheckUsers(it)
// only admin user can access timer page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("filemanagement.html") {
before {
val user = CheckUsers(it)
// only admin user can access file management page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("broadcastzones.html") {
before {
val user = CheckUsers(it)
// only admin user can access broadcast zones page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("overview.html") {
before { CheckUsers(it) }
}
path("streamerstatus.html") {
before { CheckUsers(it) }
}
path("usermanagement.html") {
before {
val user = CheckUsers(it)
// only admin user can access user management page
if (GetAdminUserFromConfig() != user) {
it.redirect("overview.html")
}
}
}
path("api") {
//TODO https://stackoverflow.com/questions/70002015/streaming-into-audio-element
path("LiveAudio") {
post { ctx ->
val json: JsonNode = objectmapper.readTree(ctx.body())
val broadcastzone = json.get("broadcastzone")?.asText("") ?: ""
val command = json.get("command")?.asText("") ?: ""
if (command == "Open" || command == "Close") {
if (broadcastzone.isNotEmpty()) {
val bc = Get_Barix_Connection_by_ZoneName(broadcastzone)
if (bc != null) {
val key = ctx.cookie("client-stream-id")
if (command == "Open") {
// open command
if (!key.isNullOrEmpty()) {
// ada connection sebelumnya, kemungkinan reconnect
val prev = WsContextMap[key]
if (prev != null) {
prev.bc?.Remove_Mp3_Consumer(key)
prev.ws?.closeSession(
WsCloseStatus.NORMAL_CLOSURE,
"Reopen Live Audio Stream"
)
}
WsContextMap.remove(key)
ctx.cookie("client-stream-id", "")
}
val newkey = UUID.randomUUID().toString()
.also { ctx.cookie("client-stream-id", it) }
WsContextMap[newkey] = LiveListenData(newkey, bc, null)
ResultMessageString(ctx, 200, newkey)
} else {
// close command
if (!key.isNullOrEmpty()) {
// close connection
val prev = WsContextMap[key]
if (prev != null) {
prev.bc?.Remove_Mp3_Consumer(key)
prev.ws?.closeSession(
WsCloseStatus.NORMAL_CLOSURE,
"Close Live Audio Stream"
)
}
WsContextMap.remove(key)
ctx.cookie("client-stream-id", "")
ResultMessageString(ctx, 200, "OK")
} else ResultMessageString(ctx, 400, "No client-id cookie found")
}
} else ResultMessageString(ctx, 400, "Broadcastzone not found")
} else ResultMessageString(ctx, 400, "Invalid broadcastzone")
} else ResultMessageString(ctx, 400, "Invalid command")
}
ws("ws") { wscontext ->
wscontext.onConnect {
val key = it.cookie("client-stream-id")
if (!key.isNullOrEmpty()) {
val lld = WsContextMap[key]
if (lld != null) {
it.enableAutomaticPings()
lld.ws = it
lld.bc?.Add_Mp3_Consumer(key) { mp3data ->
try {
if (it.session.isOpen) {
it.send(ByteBuffer.wrap(mp3data))
}
} catch (e: Exception) {
Logger.error { "Error sending LiveAudio mp3 data for key $key, Message: ${e.message}" }
}
}
} else {
it.closeSession(WsCloseStatus.POLICY_VIOLATION, "WsContextMap key not found")
Logger.info { "LiveAudio WebSocket connection rejected, WsContextMap key not found" }
}
} else {
it.closeSession(WsCloseStatus.POLICY_VIOLATION, "Invalid client-stream-id")
Logger.info { "LiveAudio WebSocket connection rejected, invalid client-stream-id" }
}
}
wscontext.onClose {
val key = it.cookie("client-stream-id")
if (!key.isNullOrEmpty()) {
val lld = WsContextMap[key]
lld?.bc?.Remove_Mp3_Consumer(key)
lld?.ws?.closeSession()
lld?.bc = null
lld?.ws = null
WsContextMap.remove(key)
Logger.info { "LiveAudio WebSocket closed for key $key" }
}
}
wscontext.onError {
val key = it.cookie("client-stream-id")
val msg = it.error()?.message ?: ""
Logger.info { "LiveAudio WebSocket error for key $key, Message: $msg" }
}
}
}
path("VoiceType") {
get {
it.result(objectmapper.writeValueAsString(VoiceType.entries.map { vt -> vt.name }))
}
}
path("Category") {
get {
it.result(objectmapper.writeValueAsString(Category.entries.map { cat -> cat.name }))
}
}
path("Language") {
get {
it.result(objectmapper.writeValueAsString(Language.entries.map { lang -> lang.name }))
}
}
path("ScheduleDay") {
get {
it.result(objectmapper.writeValueAsString(ScheduleDay.entries.map { day -> day.toString() }))
}
}
path("ListFiles/{Language}/{VoiceType}/{Category}") {
get { ctx ->
val language = ctx.pathParam("Language")
val voiceType = ctx.pathParam("VoiceType")
val category = ctx.pathParam("Category")
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
if (ValidString(voiceType) && VoiceType.entries.any { vt -> vt.name == voiceType }) {
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
val dir = Somecodes.SoundbankDirectory(
Language.valueOf(language),
VoiceType.valueOf(voiceType),
Category.valueOf(category)
)
if (Files.isDirectory(dir)) {
val list = ListAudioFiles(dir)
ctx.result(objectmapper.writeValueAsString(list))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Directory does not exist")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
}
}
path("SoundBank") {
get("FileCheck"){ ctx ->
db.soundDB.FileCheck { result ->
ctx.result(objectmapper.writeValueAsString(result))
}
}
get("List") { ctx ->
db.soundDB.Get({
ctx.result(MariaDB.ArrayListtoString(db.soundDB.List))
}, { msgFail ->
ctx.status(500)
.result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
get("ListFiles") {
it.result(objectmapper.writeValueAsString(ListAudioFiles(Somecodes.Soundbank_directory)))
}
get("GetPhrases/{Language}/{VoiceType}") {
val language = it.pathParam("Language")
val voiceType = it.pathParam("VoiceType")
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
if (ValidString(voiceType) && VoiceType.entries.any { vt -> vt.name == voiceType }) {
val phrases = db.soundDB.List
.asSequence()
.filter { sb -> sb.Language == language }
.filter { sb -> sb.VoiceType == voiceType }
.filter { sb -> sb.Category == Category.Phrase.name }
.distinctBy { sb -> sb.TAG }
.sortedBy { sb -> sb.TAG }
.toList()
it.result(objectmapper.writeValueAsString(phrases))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
}
get("AirlineTags") { ctx ->
val tags = db.soundDB.Get_AirlineCode_Tags()
val value = db.soundDB.List
.asSequence()
.filter { f1 -> f1.Category.equals(Category.Airplane_Name.name, true) }
.filter { f2 -> tags.any { t -> t.equals(f2.TAG, true) } }
.distinctBy { it.TAG }
.sortedBy { it.TAG }
.map { KeyValueMessage(it.TAG, it.Description) }
.toList()
ctx.result(objectmapper.writeValueAsString(value))
}
get("CityTags") { ctx ->
val value = db.soundDB.List
.filter { it.Category == Category.City.name }
.distinctBy { it.TAG }
.sortedBy { it.TAG }
.map { KeyValueMessage(it.TAG, it.Description) }
ctx.result(objectmapper.writeValueAsString(value))
}
post("Add") {
try {
val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java)
if (addvalue.isNotEmpty()) {
// check apakah TAG sudah ada untuk language, category dan voicetype yang sama
val exists = db.soundDB.List.any { sb ->
sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category && sb.VoiceType == addvalue.VoiceType
}
if (!exists) {
val absolutepath = Somecodes.SoundbankDirectory(
Language.valueOf(addvalue.Language),
VoiceType.valueOf(addvalue.VoiceType),
Category.valueOf(addvalue.Category)
).resolve(addvalue.Path)
if (ValidFile(absolutepath)) {
addvalue.Path = absolutepath.toAbsolutePath().toString()
if (db.soundDB.Add(addvalue)) {
db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to add soundbank to database")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Path, file does not exist")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("TAG=${addvalue.TAG} already exists for the same Language=${addvalue.Language} and Category=${addvalue.Category}")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Some fields are empty")))
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid request body, Message: ${e.message}")))
}
}
delete("List") {
// truncate soundbank table
if (db.soundDB.Clear()) {
db.soundDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate soundbank table")))
}
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.soundDB.DeleteByIndex(index.toInt())) {
db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted sound bank with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete soundbank with index $index")))
}
}
}
patch("UpdateByIndex/{index}") {
// update by index
val index = it.pathParam("index").toUIntOrNull()
if (index != null) {
val sb = db.soundDB.List.find { xx -> xx.index == index }
if (sb != null) {
try {
val newsb = objectmapper.readValue(it.body(), Soundbank::class.java)
if (newsb.isNotEmpty()) {
var varchanged = false
if (newsb.Description != sb.Description) {
sb.Description = newsb.Description
varchanged = true
}
if (newsb.TAG != sb.TAG) {
sb.TAG = newsb.TAG
varchanged = true
}
if (newsb.ValidCategory()) {
if (newsb.Category != sb.Category) {
sb.Category = newsb.Category
varchanged = true
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
return@patch
}
if (newsb.ValidLanguage()) {
if (newsb.Language != sb.Language) {
sb.Language = newsb.Language
varchanged = true
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
return@patch
}
if (newsb.ValidVoiceType()) {
if (newsb.VoiceType != sb.VoiceType) {
sb.VoiceType = newsb.VoiceType
varchanged = true
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
return@patch
}
val newpath = Somecodes.SoundbankDirectory(
newsb.Language,
newsb.VoiceType,
newsb.Category
).resolve(newsb.Path)
if (ValidFile(newpath)) {
if (newpath.toAbsolutePath().toString() != sb.Path) {
sb.Path = newpath.toAbsolutePath().toString()
varchanged = true
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid path")))
return@patch
}
if (varchanged) {
if (db.soundDB.UpdateByIndex(index.toInt(), sb)) {
db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update soundbank with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for soundbank with index $index")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Unable to parse request body to Soundbank, Message: ${e.message}")))
}
} else it.status(404)
.result(objectmapper.writeValueAsString(resultMessage("Soundbank with index $index not found")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
}
get("ExportXLSX") {
val xlsxdata = db.soundDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"soundbank.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export soundbank to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.soundDB.Import_XLSX(xlsx)) {
db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import soundbank from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
path("MessageBank") {
get("List") { ctx ->
// get messagebank list
db.messageDB.Get({
ctx.result(MariaDB.ArrayListtoString(db.messageDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
get("MessageIDs") { ctx ->
val value = db.messageDB.List
.distinctBy { it.ANN_ID }
.sortedBy { it.ANN_ID }
.map { KeyValueMessage(it.ANN_ID.toString(), it.Description) }
ctx.result(objectmapper.writeValueAsString(value))
}
post("Add") {
val json: JsonNode = objectmapper.readTree(it.body())
Logger.info { "Add MessageBank JSON: $json" }
val description = json.get("Description")?.asText("") ?: ""
val language = json.get("Language")?.asText("") ?: ""
val ann_id = json.get("ANN_ID")?.asInt()?.toUInt() ?: 0u
val voice_type = json.get("Voice_Type")?.asText("") ?: ""
val message_detail = json.get("Message_Detail")?.asText("") ?: ""
val message_tags = json.get("Message_TAGS")?.asText("") ?: ""
if (description.isNotEmpty()) {
if (language.isNotEmpty() && Language.entries.any { lang -> lang.name == language }) {
if (ann_id > 0u) {
if (voice_type.isNotEmpty() && VoiceType.entries.any { vt -> vt.name == voice_type }) {
if (message_detail.isNotEmpty()) {
if (message_tags.isNotEmpty()) {
val mb = Messagebank(
0u,
description,
language,
ann_id,
voice_type,
message_detail,
message_tags
)
val existed =
db.messageDB.List.any { it.ANN_ID == mb.ANN_ID && it.Language == mb.Language && it.Voice_Type == mb.Voice_Type }
if (!existed) {
if (db.messageDB.Add(mb)) {
db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to add messagebank to database")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Messagebank with ANN_ID=${mb.ANN_ID}, Language=${mb.Language} and Voice_Type=${mb.Voice_Type} already exists")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Message_TAGS")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Message_Detail")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Voice_Type")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid ANN_ID")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
}
delete("List") {
// truncate messagebank table
if (db.messageDB.Clear()) {
db.messageDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate messagebank table")))
}
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.messageDB.DeleteByIndex(index.toInt())) {
db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted message bank with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete messagebank with index $index")))
}
}
}
patch("UpdateByIndex/{index}") {
// update messagebank by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val mb = db.messageDB.List.find { xx -> xx.index == index }
if (mb == null) {
it.status(404)
.result(objectmapper.writeValueAsString(resultMessage("Messagebank with index $index not found")))
} else {
val json: JsonNode = objectmapper.readTree(it.body())
if (json.isEmpty) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _description = json.get("Description").asText("")
val _language = json.get("Language").asText("")
val _ann_id = json.get("ANN_ID").asInt().toUInt()
val _voice_type = json.get("Voice_Type").asText("")
val _message_detail = json.get("Message_Detail").asText("")
val _message_tags = json.get("Message_TAGS").asText("")
var changed = false
if (ValidString(_description) && _description != mb.Description) {
mb.Description = _description
changed = true
}
if (ValidString(_language) && _language != mb.Language) {
mb.Language = _language
changed = true
}
if (_ann_id > 0u && _ann_id != mb.ANN_ID) {
mb.ANN_ID = _ann_id
changed = true
}
if (ValidString(_voice_type) && _voice_type != mb.Voice_Type) {
if (VoiceType.entries.any { vt -> vt.name == _voice_type }) {
mb.Voice_Type = _voice_type
changed = true
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Voice_Type")))
return@patch
}
}
if (ValidString(_message_detail) && _message_detail != mb.Message_Detail) {
mb.Message_Detail = _message_detail
changed = true
}
if (ValidString(_message_tags) && _message_tags != mb.Message_TAGS) {
mb.Message_TAGS = _message_tags
changed = true
}
if (changed) {
if (db.messageDB.UpdateByIndex(index.toInt(), mb)) {
db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update messagebank with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for messagebank with index $index")))
}
}
}
}
get("ExportXLSX") {
val xlsxdata = db.messageDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"messagebank.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export messagebank to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.messageDB.Import_XLSX(xlsx)) {
db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import messagebank from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
path("LanguageLink") {
get("List") { ctx ->
db.languageDB.Get({
// get language link list
ctx.result(MariaDB.ArrayListtoString(db.languageDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
post("DefaultInit") { ctx ->
// clear languageDB and init with default values
// default value : every CITY in soundDB will have language linked to
db.languageDB.Clear()
db.soundDB.Get_City_Tags().forEach { city ->
val newvalue = LanguageLink(0u, city, Language.DefaultLanguageLink())
db.languageDB.Add(newvalue)
}
db.languageDB.Resort()
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
}
post("Add") {
// Parse JSON from request body
val json: JsonNode = objectmapper.readTree(it.body())
val tag = json.get("tag").asText("")
val languages = json.get("language").asText("").split(";")
if (ValidString(tag)) {
if (languages.all { xx ->
Language.entries.any { yy ->
yy.name.equals(
xx,
true
)
}
}) {
if (!db.languageDB.List.any { ll -> ll.TAG.equals(tag, true) }) {
val newvalue = LanguageLink(0u, tag, languages.joinToString(";"))
if (db.languageDB.Add(newvalue)) {
db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to add language link to database")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("TAG=$tag already exists")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Contains unsupported language")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid tag or language")))
}
}
delete("List") {
// truncate language link table
if (db.languageDB.Clear()) {
db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate language link table")))
}
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.languageDB.DeleteByIndex(index.toInt())) {
db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted language link with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete language link with index $index")))
}
}
}
patch("UpdateByIndex/{index}") {
// update by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val ll = db.languageDB.List.find { xx -> xx.index == index }
if (ll == null) {
it.status(404)
.result(objectmapper.writeValueAsString(resultMessage("Language link with index $index not found")))
} else {
val json: JsonNode = objectmapper.readTree(it.body())
if (json.isEmpty) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _tag = json.get("tag").asText("")
val _language = json.get("language").asText("")
var changed = false
if (ValidString(_language) && _language != ll.Language) {
ll.Language = _language
changed = true
}
if (ValidString(_tag) && _tag != ll.TAG) {
ll.TAG = _tag
changed = true
}
if (changed) {
if (db.languageDB.UpdateByIndex(index.toInt(), ll)) {
db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update language link with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for language link with index $index")))
}
}
}
}
get("ExportXLSX") {
val xlsxdata = db.languageDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"languagelink.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export language link to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.languageDB.Import_XLSX(xlsx)) {
db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import language link from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
path("ScheduleBank") {
get("List") { ctx ->
db.scheduleDB.Get({
// get timer list
ctx.result(MariaDB.ArrayListtoString(db.scheduleDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
// get timer list
}
delete("List") {
// truncate timer table
if (db.scheduleDB.Clear()) {
db.scheduleDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate schedulebank table")))
}
}
post("Add") {
val json: JsonNode = objectmapper.readTree(it.body())
val description = json.get("Description")?.asText("") ?: ""
val day = json.get("Day")?.asText("") ?: ""
val time = json.get("Time")?.asText("") ?: ""
val soundpath = json.get("Soundpath")?.asText("") ?: ""
val repeat = json.get("Repeat")?.asInt()?.toUByte() ?: 0u
val enable = json.get("Enable")?.asBoolean() ?: false
val broadcast_zones = json.get("BroadcastZones")?.asText("") ?: ""
val language = json.get("Language")?.asText("") ?: ""
if (ValidString(description)) {
if (ValidScheduleDay(day)) {
if (ValidScheduleTime(time)) {
if (ValidString(soundpath)) {
if (repeat in 0u..127u) {
if (ValidString(broadcast_zones)) {
val zones = broadcast_zones.split(";")
if (zones.all { zz ->
db.broadcastDB.List.any { xx ->
xx.description.equals(
zz,
true
)
}
}) {
if (ValidString(language)) {
val newvalue = ScheduleBank(
0u,
description,
day,
time,
soundpath,
repeat,
enable,
broadcast_zones,
language
)
if (db.scheduleDB.Add(newvalue)) {
db.scheduleDB.Resort()
it.result(
objectmapper.writeValueAsString(
resultMessage(
"OK"
)
)
)
} else it.status(500)
.result(
objectmapper.writeValueAsString(
resultMessage("Failed to add schedule to database")
)
)
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Contains unsupported BroadcastZones")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid BroadcastZones")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Repeat, must be between 0-127")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Soundpath")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Time format, must be HH:mm")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Day format")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.scheduleDB.DeleteByIndex(index.toInt())) {
db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted schedule bank with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete schedule with index $index")))
}
}
}
patch("UpdateByIndex/{index}") {
// update by index
val index = it.pathParam("index").toUIntOrNull()
if (index != null) {
val sb = db.scheduleDB.List.find { xx -> xx.index == index }
if (sb != null) {
val json = objectmapper.readTree(it.body())
val _description = json.get("Description").asText("")
val _time = json.get("Time").asText("")
val _day = json.get("Day").asText("")
val _soundpath = json.get("Soundpath").asText("")
val _repeat = json.get("Repeat").asInt().toUByte()
val _enable = json.get("Enable").asBoolean()
val _broadcast_zones = json.get("BroadcastZones").asText("")
val _language = json.get("Language").asText("")
val newsb = ScheduleBank(
index,
_description,
_day,
_time,
_soundpath,
_repeat,
_enable,
_broadcast_zones,
_language
)
if (newsb.isNotEmpty()) {
if (!sb.isEqual(newsb)) {
if (db.scheduleDB.UpdateByIndex(index.toInt(), newsb)) {
db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update schedule with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for schedule with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Some fields are empty")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Schedule with index $index not found")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
}
get("ExportXLSX") {
val xlsxdata = db.scheduleDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"schedulebank.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export schedulebank to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.scheduleDB.Import_XLSX(xlsx)) {
db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import schedulebank from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
get("GetMessageAndBroadcastZones") {
val result = object {
val messages = db.messageDB.List
.filter { mb ->
!mb.Message_Detail.contains("[") && !mb.Message_Detail.contains(
"]"
)
}
.map { mb -> "${mb.Description} [${mb.ANN_ID}]" }
val broadcastzones = db.broadcastDB.List
}
it.result(objectmapper.writeValueAsString(result))
}
// Kirim list language dari Messagebank berdasarkan ANN_ID
get("GetLanguageList/{ANN_ID}") { get1 ->
val langlist = db.messageDB.List
.filter { it.ANN_ID == get1.pathParam("ANN_ID").toInt().toUInt() }
.map { it.Language }.distinct()
get1.result(objectmapper.writeValueAsString(langlist))
}
}
path("UserManagement") {
get("List") { ctx ->
db.userDB.Get({
ctx.result(objectmapper.writeValueAsString(db.userDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
delete("List") {
if (db.userDB.Clear()) {
db.userDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate user table")))
}
}
post("Add") { ctx ->
val json: JsonNode = objectmapper.readTree(ctx.body())
val username = json.get("username").asText("")
val password = json.get("password").asText("")
val location = json.get("location").asText("N/A")
val airline_tags = json.get("airline_tags").asText("")
val city_tags = json.get("city_tags").asText("")
val messagebank_ann_id = json.get("messagebank_ann_id").asText("")
val broadcastzones = json.get("broadcastzones").asText("")
// revisi 30012026
// city, airline, messagebank_ann_id boleh kosong, untuk user yang paging saja
// location juga optional
// yang wajib ada adalah username, password, broadcastzones
if (ValidStrings(username, password, broadcastzones)) {
if (!db.userDB.Username_exists(username)) {
if (ValidString(airline_tags)) {
// ada airline_tags, berarti harus di cek apakah ada di table soundbank
// kalau tidak ada airline_tags, tidak perlu cek
val atags = airline_tags.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val airlinetags = db.soundDB.Get_AirlineCode_Tags()
val missing_airlinetags = ArrayList<String>()
atags.forEach { tag ->
if (!airlinetags.any { it.equals(tag, true) }) {
missing_airlinetags.add(tag)
}
}
if (missing_airlinetags.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"Airline tags not found in soundbank: ${
missing_airlinetags.joinToString(
", "
)
}"
)
)
)
return@post
}
}
if (ValidString(city_tags)) {
// ada city_tags, berarti harus di cek apakah ada di table soundbank
// kalau tidak ada city_tags, tidak perlu cek
val ctags = city_tags.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val citytags = db.soundDB.Get_City_Tags()
val missing_citytags = ArrayList<String>()
ctags.forEach { tag ->
if (!citytags.any { it.equals(tag, true) }) {
missing_citytags.add(tag)
}
}
if (missing_citytags.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"City tags not found in soundbank: ${
missing_citytags.joinToString(
", "
)
}"
)
)
)
return@post
}
}
if (ValidString(messagebank_ann_id)) {
// ada messagebank_ann_id, berarti harus di cek apakah ada di table messagebank
// kalau tidak ada messagebank_ann_id, tidak perlu cek
val mbids = messagebank_ann_id.split(";")
.map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
.mapNotNull { it.toUIntOrNull() }
val mbankids = db.messageDB.Get_MessageID_List()
if (!mbids.all { id -> mbankids.any { it == id } }) {
ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Some ANN_ID not found in Messagebank")))
return@post
}
}
// check apakah semua broadcast zones ada di soundbank
val bzdesc =
broadcastzones.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val bzlist = db.broadcastDB.Get_BroadcastZone_List()
val missing_broadcastzones = ArrayList<String>()
bzdesc.forEach { bz ->
if (!bzlist.any { it.equals(bz, true) }) {
missing_broadcastzones.add(bz)
}
}
if (missing_broadcastzones.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"Broadcast zone tags not found in soundbank: ${
missing_broadcastzones.joinToString(
", "
)
}"
)
)
)
return@post
}
// sampe sini valid semua
// semua valid, tambain ke database
val newuser = UserDB(
0u,
username,
password,
location,
airline_tags,
city_tags,
messagebank_ann_id,
broadcastzones
)
if (db.userDB.Add(newuser)) {
db.userDB.Resort()
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else ctx.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to add user to database")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString("Username already exists"))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Not all fields have value")))
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.userDB.DeleteByIndex(index.toInt())) {
db.userDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted user with index $index")
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete user with index $index")))
}
}
patch("UpdateByIndex/{index}") { ctx ->
// update by index
val index = ctx.pathParam("index").toUIntOrNull()
if (index != null) {
val user = db.userDB.List.find { xx -> xx.index == index }
if (user != null) {
val json: JsonNode = objectmapper.readTree(ctx.body())
if (!json.isEmpty) {
val _username = json.get("username").asText("")
val _password = json.get("password").asText("")
val _location = json.get("location").asText("N/A")
val _airline_tags = json.get("airline_tags").asText("")
val _city_tags = json.get("city_tags").asText("")
val _messagebank_ann_id = json.get("messagebank_ann_id").asText("")
val _broadcastzones = json.get("broadcastzones").asText("")
// revisi 30012026
// city, airline, messagebank_ann_id boleh kosong, untuk user yang paging saja
// location juga optional
// yang wajib ada adalah username, password, broadcastzones
if (ValidStrings(_username, _password, _broadcastzones)) {
val _otherusername = db.userDB.List.find { xx ->
xx.username.equals(
_username,
true
) && xx.index != index
}
if (_otherusername != null) {
Logger.info { "Found other username with same name: ${_otherusername.username} at index ${_otherusername.index}" }
ctx.status(400).result(
objectmapper.writeValueAsString(
resultMessage("Username already exists for another user")
)
)
return@patch
}
if (ValidString(_airline_tags)) {
// ada airline_tags, berarti harus di cek apakah ada di table soundbank
// kalau tidak ada airline_tags, tidak perlu cek
val atags = _airline_tags.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val airlinetags = db.soundDB.Get_AirlineCode_Tags()
val missing_airlinetags = ArrayList<String>()
atags.forEach { tag ->
if (!airlinetags.any { it.equals(tag, true) }) {
missing_airlinetags.add(tag)
}
}
if (missing_airlinetags.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"Airline tags not found in soundbank: ${
missing_airlinetags.joinToString(
", "
)
}"
)
)
)
return@patch
}
}
if (ValidString(_city_tags)) {
// ada city_tags, berarti harus di cek apakah ada di table soundbank
// kalau tidak ada city_tags, tidak perlu cek
val ctags = _city_tags.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val citytags = db.soundDB.Get_City_Tags()
val missing_citytags = ArrayList<String>()
ctags.forEach { tag ->
if (!citytags.any { it.equals(tag, true) }) {
missing_citytags.add(tag)
}
}
if (missing_citytags.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"City tags not found in soundbank: ${
missing_citytags.joinToString(
", "
)
}"
)
)
)
return@patch
}
}
if (ValidString(_messagebank_ann_id)) {
// ada messagebank_ann_id, berarti harus di cek apakah ada di table messagebank
// kalau tidak ada messagebank_ann_id, tidak perlu cek
val mbids = _messagebank_ann_id.split(";")
.map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
.mapNotNull { it.toUIntOrNull() }
val mbankids = db.messageDB.Get_MessageID_List()
val missing_broadcastids = ArrayList<UInt>()
mbids.forEach { mbid ->
if (!mbankids.any { it == mbid }) {
missing_broadcastids.add(mbid)
}
}
if (missing_broadcastids.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"ANN_ID not found in Messagebank: ${
missing_broadcastids.joinToString(
", "
)
}"
)
)
)
return@patch
}
}
val bzdesc = _broadcastzones.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val bzlist = db.broadcastDB.Get_BroadcastZone_List()
val missing_broadcastzones = ArrayList<String>()
bzdesc.forEach { bz ->
if (!bzlist.any { it.equals(bz, true) }) {
missing_broadcastzones.add(bz)
}
}
if (missing_broadcastzones.isNotEmpty()) {
ctx.status(400)
.result(
objectmapper.writeValueAsString(
resultMessage(
"Broadcast zone tags not found in soundbank: ${
missing_broadcastzones.joinToString(
", "
)
}"
)
)
)
return@patch
}
// sampe sini valid semua
val editeduser = UserDB(
index,
_username,
_password,
_location,
_airline_tags,
_city_tags,
_messagebank_ann_id,
_broadcastzones
)
if (!user.isEqual(editeduser)) {
if (db.userDB.UpdateByIndex(
index.toInt(),
editeduser
)
) {
db.userDB.Resort()
ctx.result(
objectmapper.writeValueAsString(
resultMessage("OK")
)
)
} else ctx.status(500).result(
objectmapper.writeValueAsString(
resultMessage("Failed to update user with index $index")
)
)
} else ctx.status(400).result(
objectmapper.writeValueAsString(resultMessage("Nothing has changed for user with index $index"))
)
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Not all fiels have value")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("User with index $index not found")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
}
get("ExportXLSX") {
val xlsxdata = db.userDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"userdb.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export user table to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.userDB.Import_XLSX(xlsx)) {
db.userDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import user table from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
get("GetMessageAndBroadcastZones") {
val result = object {
val messages = db.messageDB.List
val broadcastzones = db.broadcastDB.List
}
it.result(objectmapper.writeValueAsString(result))
}
}
path("Log") {
get("List") { get1 ->
val logdate = get1.queryParam("date") ?: ""
val logfilter = get1.queryParam("filter")
db.logDB.GetLogForHtml(logdate, logfilter, { loglist ->
get1.result(objectmapper.writeValueAsString(loglist))
}, { msgFail ->
get1.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
get("ExportXLSX") { get1 ->
val logdate = get1.queryParam("date") ?: ""
val logfilter = get1.queryParam("filter") ?: ""
if (ValiDateForLogHtml(logdate)) {
val xlsxdata = if (ValidString(logfilter)) {
db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), logfilter)
} else {
db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), "")
}
if (xlsxdata != null) {
get1.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
get1.header("Content-Disposition", "attachment; filename=\"log_$logdate.xlsx\"")
get1.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
get1.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export log to XLSX")))
}
} else get1.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid logdate")))
}
}
path("BroadcastZones") {
get("List") { ctx ->
db.broadcastDB.Get({
ctx.result(MariaDB.ArrayListtoString(db.broadcastDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
get("BroadcastZoneDescriptions") { ctx ->
val value = db.broadcastDB.List
.distinctBy { it.description }
.map { it.description }
ctx.result(objectmapper.writeValueAsString(value))
}
delete("List") {
// truncate broadcast zones table
if (db.broadcastDB.Clear()) {
db.broadcastDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate broadcast zones table")))
}
}
post("Add") {
val json: JsonNode = objectmapper.readTree(it.body())
val _description = json.get("description").asText("")
val _soundchannel = json.get("SoundChannel").asText("")
val _box = json.get("Box").asText("")
val _relay = json.get("Relay").asText("")
if (ValidString(_description)) {
if (ValidString(_soundchannel)) {
if (ValidString(_box)) {
if (ValidString(_relay)) {
val newbp =
BroadcastZones(0u, _description, _soundchannel, _box, _relay)
if (db.broadcastDB.Add(newbp)) {
db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to add broadcast zone to database")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Relay")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Box")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid SoundChannel")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid description")))
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.broadcastDB.DeleteByIndex(index.toInt())) {
db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted broadcast zone with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete broadcast zone with index $index")))
}
}
}
patch("UpdateByIndex/{index}") {
// update by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val bz = db.broadcastDB.List.find { xx -> xx.index == index }
if (bz == null) {
it.status(404)
.result(objectmapper.writeValueAsString(resultMessage("Broadcast zone with index $index not found")))
} else {
val json: JsonNode = objectmapper.readTree(it.body())
if (json.isEmpty) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _description = json.get("description").asText("")
val _soundchannel = json.get("SoundChannel").asText("")
val _box = json.get("Box").asText("")
val _relay = json.get("Relay").asText("")
var changed = false
if (ValidString(_description) && _description != bz.description) {
bz.description = _description
changed = true
}
if (ValidString(_soundchannel) && _soundchannel != bz.SoundChannel) {
bz.SoundChannel = _soundchannel
changed = true
}
if (ValidString(_box) && _box != bz.id) {
bz.id = _box
changed = true
}
if (ValidString(_relay) && _relay != bz.bp) {
bz.bp = _relay
changed = true
}
if (changed) {
if (db.broadcastDB.UpdateByIndex(index.toInt(), bz)) {
db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update broadcast zone with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for broadcast zone with index $index")))
}
}
}
}
get("ExportXLSX") {
val xlsxdata = db.broadcastDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"broadcastzones.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export broadcast zones to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.broadcastDB.Import_XLSX(xlsx)) {
db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import broadcast zones from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
path("SoundChannel") {
get("List") { ctx ->
db.soundchannelDB.Get({
ctx.result(MariaDB.ArrayListtoString(db.soundchannelDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
get("SoundChannelDescriptions") {
it.result(objectmapper.writeValueAsString(db.soundchannelDB.Get_SoundChannel_List()))
}
delete("List") {
// truncate sound channel table
if (db.soundchannelDB.Clear()) {
db.soundchannelDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate sound channel table")))
}
}
patch("UpdateByIndex/{index}") {
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val sc = db.soundchannelDB.List.find { xx -> xx.index == index }
if (sc == null) {
it.status(404)
.result(objectmapper.writeValueAsString(resultMessage("Sound channel with index $index not found")))
} else {
val json: JsonNode = objectmapper.readTree(it.body())
if (json.isEmpty) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _channel = json.get("description").asText("")
val _ip = json.get("ip").asText("")
if (ValidString(_channel)) {
if (ValidIPV4(_ip)) {
if (_channel.equals(sc.channel) && _ip.equals(sc.ip)) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for sound channel with index $index")))
return@patch
} else {
// cek apakah ada soundchannel lain yang pakai ip dan channel yang sama
val othersc =
db.soundchannelDB.List.filter { sc -> sc.ip == _ip }
.filter { sc -> sc.index != index }
if (othersc.isNotEmpty()) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("This IP address is already used by another sound channel")))
} else {
// ada sesuatu yang ganti
val newsc = SoundChannel(0u, _channel, _ip)
if (db.soundchannelDB.UpdateByIndex(index.toInt(), newsc)) {
db.soundchannelDB.Resort()
it.result(
objectmapper.writeValueAsString(
resultMessage(
"OK"
)
)
)
} else {
it.status(500)
.result(
objectmapper.writeValueAsString(
resultMessage("Failed to update sound channel with index $index")
)
)
}
}
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid IP address")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid channel")))
}
}
}
}
}
get("ExportXLSX") {
val xlsxdata = db.soundchannelDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"soundchannel.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export sound channel to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.soundchannelDB.Import_XLSX(xlsx)) {
db.soundchannelDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import sound channel from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
path("QueuePaging") {
get("List") { ctx ->
db.queuepagingDB.Get({
ctx.result(MariaDB.ArrayListtoString(db.queuepagingDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
delete("List") {
// truncate queue paging table
if (db.queuepagingDB.Clear()) {
db.queuepagingDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue paging table")))
}
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.queuepagingDB.DeleteByIndex(index.toInt())) {
db.queuepagingDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted queue paging with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue paging with index $index")))
}
}
}
}
path("QueueTable") {
get("List") { ctx ->
db.queuetableDB.Get({
ctx.result(MariaDB.ArrayListtoString(db.queuetableDB.List))
}, { msgFail ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail)))
})
}
delete("List") {
// truncate queue table
if (db.queuetableDB.Clear()) {
db.queuetableDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue sound table")))
}
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.queuetableDB.DeleteByIndex(index.toInt())) {
db.queuetableDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logDB.Add("AAS", "Deleted queue sound with index $index")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue sound with index $index")))
}
}
}
}
path("FileManager") {
get("PagingResultList") {
it.result(objectmapper.writeValueAsString(ListAudioFiles(Somecodes.PagingResult_directory)))
}
delete("DeleteOldPagingResultFiles/{days}") {
// kalau tidak ada, default 7 hari
val days = it.pathParam("days").toIntOrNull() ?: 7
Somecodes.Delete_Old_PagingResult_Files(days) { success, failed ->
it.result(objectmapper.writeValueAsString(resultMessage("Old paging result files deleted. Deleted: $success, Failed: $failed")))
}
}
delete("ClearPagingResultFiles") {
Somecodes.Clear_PagingResult_Directory { success, failed ->
it.result(objectmapper.writeValueAsString(resultMessage("Paging result files cleared. Deleted: $success, Failed: $failed")))
}
}
post("DownloadPagingResultFile") {
val json: JsonNode = objectmapper.readTree(it.body())
val filename = json.get("filename").asText("")
if (filename.isNotEmpty()) {
val p: Path = Somecodes.PagingResult_directory.resolve(filename)
if (ValidFile(p)) {
it.header("Content-Disposition", "attachment; filename=\"$filename\"")
it.outputStream().use { out ->
Files.newInputStream(p).use { inp ->
inp.copyTo(out)
}
}
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("File not found")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Filename is empty")))
}
post("PlayPagingResultFile") {
val json: JsonNode = objectmapper.readTree(it.body())
val filename = json.get("filename").asText("")
if (filename.isNotEmpty()) {
val p: Path = Somecodes.PagingResult_directory.resolve(filename)
if (ValidFile(p)) {
val mimeType = Files.probeContentType(p) ?: "audio/wav"
it.contentType(mimeType)
it.header("Accept-Ranges", "bytes")
it.result(Files.newInputStream(p))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("File not found")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Filename is empty")))
}
get("SoundbankResultList") {
it.result(objectmapper.writeValueAsString(ListAudioFiles(Somecodes.SoundbankResult_directory)))
}
delete("DeleteOldSoundbankResultFiles/{days}") {
// kalau tidak ada, default 7 hari
val days = it.pathParam("days").toIntOrNull() ?: 7
Somecodes.Delete_Old_SoundbankResult_Files(days) { success, failed ->
it.result(objectmapper.writeValueAsString(resultMessage("Old soundbank result files deleted. Deleted: $success, Failed: $failed")))
}
}
delete("ClearSoundbankResultFiles") {
Somecodes.Clear_PagingResult_Directory { success, failed ->
it.result(objectmapper.writeValueAsString(resultMessage("Soundbank result files cleared. Deleted: $success, Failed: $failed")))
}
}
post("DownloadSoundbankResultFile") {
val json: JsonNode = objectmapper.readTree(it.body())
val filename = json.get("filename").asText("")
if (filename.isNotEmpty()) {
val p: Path = Somecodes.SoundbankResult_directory.resolve(filename)
if (ValidFile(p)) {
it.header("Content-Disposition", "attachment; filename=\"$filename\"")
it.outputStream().use { out ->
Files.newInputStream(p).use { inp ->
inp.copyTo(out)
}
}
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("File not found")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Filename is empty")))
}
post("PlaySoundbankResultFile") {
val json: JsonNode = objectmapper.readTree(it.body())
val filename = json.get("filename").asText("")
if (filename.isNotEmpty()) {
val p: Path = Somecodes.SoundbankResult_directory.resolve(filename)
if (ValidFile(p)) {
val mimeType = Files.probeContentType(p) ?: "audio/wav"
it.contentType(mimeType)
it.header("Accept-Ranges", "bytes")
it.result(Files.newInputStream(p))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("File not found")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Filename is empty")))
}
get("ListSoundbank/{language}/{voice}/{category}") {
val language = it.pathParam("language")
val voice = it.pathParam("voice")
val category = it.pathParam("category")
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
if (ValidString(voice) && VoiceType.entries.any { vtype -> vtype.name == voice }) {
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
val targetdir = Somecodes.SoundbankDirectory(
Language.valueOf(language),
VoiceType.valueOf(voice),
Category.valueOf(category)
)
val result = ListAudioFiles(targetdir).map { mm ->
// just the filename, without path
mm.substring(mm.lastIndexOf(File.separator) + 1)
}
it.result(objectmapper.writeValueAsString(result))
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Category parameter is invalid")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("VoiceType parameter is invalid")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Language parameter is invalid")))
}
}
// tested ok
post("UploadSoundbank/{language}/{voice}/{category}") {
val language = it.pathParam("language")
val voice = it.pathParam("voice")
val category = it.pathParam("category")
val uploaded = it.uploadedFiles()
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
if (ValidString(voice) && VoiceType.entries.any { vtype -> vtype.name == voice }) {
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
if (uploaded.isNotEmpty()) {
val targetdir = Somecodes.SoundbankDirectory(
Language.valueOf(language),
VoiceType.valueOf(voice),
Category.valueOf(category)
)
if (!Files.isDirectory(targetdir)) Files.createDirectories(targetdir)
val successfiles = mutableListOf<String>()
val failedfiles = mutableListOf<String>()
val alreadyexists = mutableListOf<String>()
uploaded.forEach { ff ->
val targetfile = targetdir.resolve(ff.filename())
if (Files.exists(targetfile)) {
alreadyexists.add(ff.filename())
} else {
try {
Files.newOutputStream(targetfile).use { out ->
ff.content().use { inp ->
inp.copyTo(out)
}
}
Logger.info { "Uploaded soundbank file to ${targetfile.toAbsolutePath()}" }
successfiles.add(ff.filename())
} catch (e: Exception) {
Logger.error {
"Failed to upload soundbank file to ${targetfile.toAbsolutePath()}: ${e.message}"
}
failedfiles.add(ff.filename())
}
}
}
if (successfiles.size == uploaded.size) {
it.result(
objectmapper.writeValueAsString(
resultMessage(
"All files uploaded successfully: ${
successfiles.joinToString(
", "
)
}"
)
)
)
} else {
it.result(
objectmapper.writeValueAsString(
resultMessage(
"Some files uploaded successfully: ${
successfiles.joinToString(
", "
)
}, failed: ${failedfiles.joinToString(", ")}, already exists: ${
alreadyexists.joinToString(
", "
)
}"
)
)
)
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Category parameter is invalid")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("VoiceType parameter is invalid")))
}
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Language parameter is invalid")))
}
}
}
path("Settings") {
path("OldResultDays") {
get {
it.result(objectmapper.writeValueAsString(resultMessage(_config.Get(configKeys.AUTO_DELETE_RESULT_DAYS.key))))
}
post {
val json: JsonNode = objectmapper.readTree(it.body())
val days = json.get("days").asInt(3)
_config.Set(configKeys.AUTO_DELETE_RESULT_DAYS.key, days.toString())
_config.Save()
Logger.info { "Changed Auto Delete Result Days to $days" }
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
}
}
path("SoundbankDirectory") {
get {
val dir = _config.Get(configKeys.SOUNDBANK_DIRECTORY.key)
it.result(objectmapper.writeValueAsString(resultMessage(dir)))
}
post {
val json: JsonNode = objectmapper.readTree(it.body())
val newdir = json.get("directory").asText("")
if (ValidDirectory(newdir)) {
_config.Set(configKeys.SOUNDBANK_DIRECTORY.key, newdir)
_config.Save()
Somecodes.Soundbank_directory = Path.of(newdir)
Logger.info { "Changed Soundbank Directory to $newdir" }
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid directory value")))
}
}
}
path("FISCode") {
get {
val value = object {
//get from config file
val GOP = _config.Get(configKeys.REMARK_GOP.key)
val GBD = _config.Get(configKeys.REMARK_GBD.key)
val GFC = _config.Get(configKeys.REMARK_GFC.key)
val FLD = _config.Get(configKeys.REMARK_FLD.key)
val defaultvoice = _config.Get(configKeys.DEFAULT_VOICE_TYPE.key)
val autodeleteresult = _config.Get(configKeys.AUTO_DELETE_RESULT_DAYS.key)
}
it.result(objectmapper.writeValueAsString(value))
}
post {
val json: JsonNode = objectmapper.readTree(it.body())
val _gop = json.get("GOP").asText("")
val _gbd = json.get("GBD").asText("")
val _gfc = json.get("GFC").asText("")
val _fld = json.get("FLD").asText("")
val defaultvoice = json.get("defaultvoice").asText("")
if (ValidString(_gop) && ValidString(_gbd) && ValidString(_gfc) && ValidString(_fld)) {
// save to config file
_config.Set(configKeys.REMARK_GOP.key, _gop)
_config.Set(configKeys.REMARK_GBD.key, _gbd)
_config.Set(configKeys.REMARK_GFC.key, _gfc)
_config.Set(configKeys.REMARK_FLD.key, _fld)
_config.Set(configKeys.DEFAULT_VOICE_TYPE.key, defaultvoice)
_config.Save()
Logger.info { "Changed FIS Codes" }
db.logDB.Add(
"AAS",
"Save FIS Codes Message: GOP=$_gop, GBD=$_gbd, GFC=$_gfc, FLD=$_fld, DefaultVoice=$defaultvoice"
)
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid FIS code value")))
}
}
}
path("WebAccess") {
get {
val value = object {
val adminpass = _config.Get(configKeys.WEBAPP_ADMIN_PASSWORD.key)
val viewerpass = _config.Get(configKeys.WEBAPP_VIEWER_PASSWORD.key)
}
it.result(objectmapper.writeValueAsString(value))
}
post {
val json: JsonNode = objectmapper.readTree(it.body())
val adminpass = json.get("adminpass").asText("")
val viewerpass = json.get("viewerpass").asText("")
if (ValidString(adminpass) && ValidString(viewerpass)) {
_config.Set(configKeys.WEBAPP_ADMIN_PASSWORD.key, adminpass)
_config.Set(configKeys.WEBAPP_VIEWER_PASSWORD.key, viewerpass)
_config.Save()
Logger.info { "Changed Web Access Passwords" }
// update userlist
userlist = listOf(
Pair(
_config.Get(configKeys.WEBAPP_ADMIN_USERNAME.key),
_config.Get(configKeys.WEBAPP_ADMIN_PASSWORD.key)
),
Pair(
_config.Get(configKeys.WEBAPP_VIEWER_USERNAME.key),
_config.Get(configKeys.WEBAPP_VIEWER_PASSWORD.key)
)
)
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Password cannot be empty")))
}
}
}
}
}
}
}.start(listenPort)
}
private fun Start_SemiAutoServer() {
Logger.info { "Starting SemiAuto web server at port ${listenPort + 1}" }
semiauto = Javalin.create { config ->
config.useVirtualThreads = true
config.staticFiles.add("/semiauto")
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
config.router.apiBuilder {
before {
//Logger.info{"${it.ip()} method ${it.method()} connect to ${it.path()}"}
}
path("/") {
get {
it.cookie("semiauto-user", "")
it.redirect("login.html")
}
}
path("logout") {
get {
it.cookie("semiauto-user", "")
it.redirect("login.html")
}
}
path("login.html") {
post {
val formuser = it.formParam("username") ?: ""
val formpass = it.formParam("password") ?: ""
if (formuser.isNotEmpty() && formpass.isNotEmpty()) {
val user = db.userDB.List.find { u -> u.username == formuser && u.password == formpass }
if (user != null) {
it.cookie("semiauto-user", user.username)
it.redirect("index.html")
db.logSemiAuto.Add(
LogSemiauto.NewLog(
"SEMIAUTOWEB",
"User logged in: ${user.username} from ip ${it.ip()}"
)
)
} else ResultMessageString(it, 400, "Invalid username or password")
} else ResultMessageString(it, 400, "Username or password cannot be empty")
}
}
path("index.html") {
before { CheckSemiAutoUsers(it) }
}
path("log.html") {
before { CheckSemiAutoUsers(it) }
}
path("api") {
path("Initialize") {
get { ctx ->
val username = ctx.cookie("semiauto-user") ?: ""
if (username.isNotEmpty()) {
val user = db.userDB.List.find { u -> u.username == username }
if (user != null) {
val result = SemiAutoInitData(username)
// messages
String_To_List(user.messagebank_ann_id).forEach { msg ->
//println("Looking for message ANN_ID: $msg")
db.messageDB.List.filter { it.ANN_ID == msg.toUInt() }.forEach { xx ->
//println("Adding message: ${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
}
}
// broacdcast zones
result.broadcastzones = String_To_List(user.broadcastzones)
// cities
String_To_List(user.city_tags).forEach { ct ->
db.soundDB.List.firstOrNull { it.TAG == ct && it.Category == Category.City.name }
?.let {
result.cities.add("${it.TAG};${it.Description}")
}
}
// airplane names
String_To_List(user.airline_tags).forEach { at ->
db.soundDB.List.firstOrNull { it.TAG == at && it.Category == Category.Airplane_Name.name }
?.let {
result.airlines.add("${it.TAG};${it.Description}")
}
}
// places
db.soundDB.List.filter { it.Category == Category.Places.name }.distinctBy { it.TAG }
.forEach { xx ->
result.places.add("${xx.TAG};${xx.Description}")
}
// shalat
db.soundDB.List.filter { it.Category == Category.Shalat.name }.distinctBy { it.TAG }
.forEach { xx ->
result.shalat.add("${xx.TAG};${xx.Description}")
}
// sequences
db.soundDB.List.filter { it.Category == Category.Sequence.name }
.distinctBy { it.TAG }.forEach { xx ->
result.sequences.add("${xx.TAG};${xx.Description}")
}
// reasons
db.soundDB.List.filter { it.Category == Category.Reason.name }.distinctBy { it.TAG }
.forEach { xx ->
result.reasons.add("${xx.TAG};${xx.Description}")
}
// procedures
db.soundDB.List.filter { it.Category == Category.Procedure.name }
.distinctBy { it.TAG }.forEach { xx ->
result.procedures.add("${xx.TAG};${xx.Description}")
}
// gates
db.soundDB.List.filter { it.Category == Category.Gate.name }.distinctBy { it.TAG }
.forEach { xx ->
result.gates.add("${xx.TAG};${xx.Description}")
}
// compensation
db.soundDB.List.filter { it.Category == Category.Compensation.name }
.distinctBy { it.TAG }.forEach { xx ->
result.compensation.add("${xx.TAG};${xx.Description}")
}
// greetings
db.soundDB.List.filter { it.Category == Category.Greeting.name }
.distinctBy { it.TAG }.forEach { xx ->
result.greetings.add("${xx.TAG};${xx.Description}")
}
ctx.result(objectmapper.writeValueAsString(result))
db.logSemiAuto.Add(
LogSemiauto.NewLog(
"SEMIAUTOWEB",
"Initialized Web variables for user: ${user.username}"
)
)
} else ResultMessageString(ctx, 400, "User not found")
} else ResultMessageString(ctx, 400, "Username is empty")
}
}
path("SemiAuto") {
post { ctx ->
val json: JsonNode = objectmapper.readTree(ctx.body())
// butuh description, languages, tags, dan broadcastzones
val description = json.get("description").asText("")
val languages = json.get("languages").asText("")
val tags = json.get("tags").asText("")
val broadcastzones = json.get("broadcastzones").asText("")
if (description.isNotEmpty()) {
if (languages.isNotEmpty()) {
if (tags.isNotEmpty()) {
if (broadcastzones.isNotEmpty()) {
val qt = QueueTable(
0u,
LocalDateTime.now().format(datetimeformat1),
"SEMIAUTOWEB",
"SOUNDBANK",
description,
tags,
broadcastzones,
1u,
languages
)
if (db.queuetableDB.Add(qt)) {
db.queuetableDB.Resort()
Logger.info { "SemiAutoWeb added to queue table: $qt" }
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.logSemiAuto.Add(
LogSemiauto.NewLog(
"SEMIAUTOWEB",
"Added to queue table: $qt"
)
)
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
} else ResultMessageString(ctx, 400, "Tags cannot be empty")
} else ResultMessageString(ctx, 400, "Languages cannot be empty")
} else ResultMessageString(ctx, 400, "Description cannot be empty")
}
}
get("ExportXLSX") { get1 ->
val logdate = get1.queryParam("date") ?: ""
val logfilter = get1.queryParam("filter") ?: ""
if (ValiDateForLogHtml(logdate)) {
val xlsxdata = if (ValidString(logfilter)) {
db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), logfilter)
} else {
db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), "")
}
if (xlsxdata != null) {
get1.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
get1.header("Content-Disposition", "attachment; filename=\"log_$logdate.xlsx\"")
get1.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
get1.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export log to XLSX")))
}
} else get1.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid date format must be dd-MM-yyyy")))
}
path("Log") {
get("/{datelog}") { ctx ->
val datelog = ctx.pathParam("datelog")
println("Request log for date: $datelog")
db.logSemiAuto.GetLogSemiAutoForHtml(datelog, null, {
ctx.result(MariaDB.ArrayListtoString(it))
}, { err ->
ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(err)))
})
}
}
}
}
}.start(listenPort + 1)
}
fun ResultMessageString(ctx: Context, code: Int, message: String) {
ctx.status(code).result(objectmapper.writeValueAsString(resultMessage(message)))
}
fun CheckSemiAutoUsers(ctx: Context) {
val user = ctx.cookie("semiauto-user")
if (user == null) {
ctx.redirect("login.html")
}
val foundUser = db.userDB.List.find { u -> u.username == user }
if (foundUser == null) {
ctx.redirect("login.html")
}
}
fun CheckUsers(ctx: Context): String? {
//val user = ctx.sessionAttribute<String?>("user")
val user = ctx.cookie("aas-user")
if (user == null) {
ctx.redirect("login.html")
return null
}
val foundUser = userlist.find { it.first == user }
if (foundUser == null) {
ctx.redirect("login.html")
return null
}
return foundUser.first
}
fun GetAdminUserFromConfig(): String {
return config.Get(configKeys.WEBAPP_ADMIN_USERNAME.key)
}
fun Get_Barix_Connection_by_ZoneName(zonename: String): BarixConnection? {
if (ValidString(zonename)) {
val bz = db.broadcastDB.List.find { it.description == zonename }
val sc = if (bz != null) db.soundchannelDB.List.find { it.channel == bz.SoundChannel } else null
val ip = sc?.ip ?: ""
if (ValidIPV4(ip)) {
// ketemu ip-nya
return StreamerOutputs[ip]
}
}
return null
}
fun Stop() {
app.stop()
semiauto.stop()
}
}