gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: Serve Nexus via Unix domain socket


From: gnunet
Subject: [libeufin] branch master updated: Serve Nexus via Unix domain socket
Date: Wed, 06 Oct 2021 17:00:31 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 16847b5  Serve Nexus via Unix domain socket
16847b5 is described below

commit 16847b5057f2e229995fdb1e62837c84930ebb62
Author: ms <ms@taler.net>
AuthorDate: Wed Oct 6 17:00:24 2021 +0200

    Serve Nexus via Unix domain socket
---
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |   17 +-
 .../tech/libeufin/nexus/server/NexusServer.kt      | 1542 ++++++++++----------
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  |    8 +-
 3 files changed, 789 insertions(+), 778 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index c950885..90f8911 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -34,10 +34,13 @@ import com.github.ajalt.clikt.parameters.types.int
 import execThrowableOrTerminate
 import com.github.ajalt.clikt.core.*
 import com.github.ajalt.clikt.parameters.options.versionOption
+import startServer
 import tech.libeufin.nexus.iso20022.parseCamtMessage
 import tech.libeufin.nexus.server.client
+import tech.libeufin.nexus.server.nexusApp
 import tech.libeufin.util.*
 import java.io.File
+import kotlin.system.exitProcess
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
 val NEXUS_DB_ENV_VAR_NAME = "LIBEUFIN_NEXUS_DB_CONNECTION"
@@ -57,14 +60,24 @@ class Serve : CliktCommand("Run nexus HTTP server") {
     }
     private val host by option().default("127.0.0.1")
     private val port by option().int().default(5001)
+    private val withUnixSocket by option(
+        help = "Bind the Sandbox to the Unix domain socket at PATH.  
Overrides" +
+                "--port, when both are given", metavar = "PATH"
+    )
     private val logLevel by option()
     override fun run() {
         setLogLevel(logLevel)
-        val dbConn = getDbConnFromEnv(NEXUS_DB_ENV_VAR_NAME)
         execThrowableOrTerminate {
-            dbCreateTables(dbConn)
+            dbCreateTables(getDbConnFromEnv(NEXUS_DB_ENV_VAR_NAME))
         }
         startOperationScheduler(client)
+        if (withUnixSocket != null) {
+            startServer(
+                withUnixSocket ?: throw Exception("Could not use the Unix 
domain socket path value!"),
+                app = nexusApp
+            )
+            exitProcess(0)
+        }
         serverMain(host, port)
     }
 }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
index 0b6098e..5fb37ea 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -150,911 +150,911 @@ fun requireBankConnection(call: ApplicationCall, 
parameterKey: String): NexusBan
 }
 
 val client = HttpClient { followRedirects = true }
-
-fun serverMain(host: String, port: Int) {
-    val server = embeddedServer(Netty, port = port, host = host) {
-        install(CallLogging) {
-            this.level = Level.DEBUG
-            this.logger = tech.libeufin.nexus.logger
-        }
-        install(ContentNegotiation) {
-            jackson {
-                enable(SerializationFeature.INDENT_OUTPUT)
-                setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
-                    
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
-                    indentObjectsWith(DefaultIndenter("  ", "\n"))
-                })
-                registerModule(KotlinModule(nullisSameAsDefault = true))
-                configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
false)
-            }
+val nexusApp: Application.() -> Unit = {
+    install(CallLogging) {
+        this.level = Level.DEBUG
+        this.logger = tech.libeufin.nexus.logger
+    }
+    install(ContentNegotiation) {
+        jackson {
+            enable(SerializationFeature.INDENT_OUTPUT)
+            setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
+                
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
+                indentObjectsWith(DefaultIndenter("  ", "\n"))
+            })
+            registerModule(KotlinModule(nullisSameAsDefault = true))
+            configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
         }
-        install(StatusPages) {
-            exception<NexusError> { cause ->
-                logger.error("Caught exception while handling 
'${call.request.uri} (${cause.reason})")
-                call.respond(
-                    status = cause.statusCode,
-                    message = ErrorResponse(
-                        code = 
TalerErrorCode.TALER_EC_LIBEUFIN_NEXUS_GENERIC_ERROR.code,
-                        hint = "nexus error, see detail",
-                        detail = cause.reason,
-                    )
+    }
+    install(StatusPages) {
+        exception<NexusError> { cause ->
+            logger.error("Caught exception while handling '${call.request.uri} 
(${cause.reason})")
+            call.respond(
+                status = cause.statusCode,
+                message = ErrorResponse(
+                    code = 
TalerErrorCode.TALER_EC_LIBEUFIN_NEXUS_GENERIC_ERROR.code,
+                    hint = "nexus error, see detail",
+                    detail = cause.reason,
                 )
-            }
-            exception<JsonMappingException> { cause ->
-                logger.error("Exception while handling '${call.request.uri}'", 
cause)
-                call.respond(
-                    HttpStatusCode.BadRequest,
-                    message = ErrorResponse(
-                        code = 
TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID.code,
-                        hint = "POSTed data was not valid",
-                        detail = cause.message ?: "not given",
-                    )
+            )
+        }
+        exception<JsonMappingException> { cause ->
+            logger.error("Exception while handling '${call.request.uri}'", 
cause)
+            call.respond(
+                HttpStatusCode.BadRequest,
+                message = ErrorResponse(
+                    code = TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID.code,
+                    hint = "POSTed data was not valid",
+                    detail = cause.message ?: "not given",
                 )
-            }
-            exception<UtilError> { cause ->
-                logger.error("Exception while handling '${call.request.uri}'", 
cause)
-                call.respond(
-                    cause.statusCode,
-                    message = ErrorResponse(
-                        code = cause.ec?.code ?: 
TalerErrorCode.TALER_EC_NONE.code,
-                        hint = "see detail",
-                        detail = cause.reason,
-                    )
+            )
+        }
+        exception<UtilError> { cause ->
+            logger.error("Exception while handling '${call.request.uri}'", 
cause)
+            call.respond(
+                cause.statusCode,
+                message = ErrorResponse(
+                    code = cause.ec?.code ?: TalerErrorCode.TALER_EC_NONE.code,
+                    hint = "see detail",
+                    detail = cause.reason,
                 )
-            }
-            exception<EbicsProtocolError> { cause ->
-                logger.error("Caught exception while handling 
'${call.request.uri}' (${cause.reason})")
-                call.respond(
-                    cause.httpStatusCode,
-                    message = ErrorResponse(
-                        code = 
TalerErrorCode.TALER_EC_LIBEUFIN_NEXUS_GENERIC_ERROR.code,
-                        hint = "EBICS protocol error",
-                        detail = cause.reason,
-                    )
+            )
+        }
+        exception<EbicsProtocolError> { cause ->
+            logger.error("Caught exception while handling 
'${call.request.uri}' (${cause.reason})")
+            call.respond(
+                cause.httpStatusCode,
+                message = ErrorResponse(
+                    code = 
TalerErrorCode.TALER_EC_LIBEUFIN_NEXUS_GENERIC_ERROR.code,
+                    hint = "EBICS protocol error",
+                    detail = cause.reason,
                 )
-            }
-            exception<Exception> { cause ->
-                logger.error("Uncaught exception while handling 
'${call.request.uri}'")
-                cause.printStackTrace()
-                call.respond(
-                    HttpStatusCode.InternalServerError,
-                    ErrorResponse(
-                        code = 
TalerErrorCode.TALER_EC_LIBEUFIN_NEXUS_UNCAUGHT_EXCEPTION.code,
-                        hint = "unexpected exception",
-                        detail = "exception message: ${cause.message}",
-                    )
+            )
+        }
+        exception<Exception> { cause ->
+            logger.error("Uncaught exception while handling 
'${call.request.uri}'")
+            cause.printStackTrace()
+            call.respond(
+                HttpStatusCode.InternalServerError,
+                ErrorResponse(
+                    code = 
TalerErrorCode.TALER_EC_LIBEUFIN_NEXUS_UNCAUGHT_EXCEPTION.code,
+                    hint = "unexpected exception",
+                    detail = "exception message: ${cause.message}",
                 )
-            }
+            )
         }
-        install(RequestBodyDecompression)
-        intercept(ApplicationCallPipeline.Fallback) {
-            if (this.call.response.status() == null) {
-                call.respondText("Not found (no route matched).\n", 
ContentType.Text.Plain, HttpStatusCode.NotFound)
-                return@intercept finish()
-            }
+    }
+    install(RequestBodyDecompression)
+    intercept(ApplicationCallPipeline.Fallback) {
+        if (this.call.response.status() == null) {
+            call.respondText("Not found (no route matched).\n", 
ContentType.Text.Plain, HttpStatusCode.NotFound)
+            return@intercept finish()
         }
-        routing {
-            get("/config") {
-                call.respond(
-                    makeJsonObject {
-                        prop("version", getVersion())
-                    }
-                )
-                return@get
-            }
-            // Shows information about the requesting user.
-            get("/user") {
-                val ret = transaction {
-                    val currentUser = authenticateRequest(call.request)
-                    UserResponse(
-                        username = currentUser.username,
-                        superuser = currentUser.superuser
-                    )
+    }
+    routing {
+        get("/config") {
+            call.respond(
+                makeJsonObject {
+                    prop("version", getVersion())
                 }
-                call.respond(ret)
-                return@get
+            )
+            return@get
+        }
+        // Shows information about the requesting user.
+        get("/user") {
+            val ret = transaction {
+                val currentUser = authenticateRequest(call.request)
+                UserResponse(
+                    username = currentUser.username,
+                    superuser = currentUser.superuser
+                )
             }
+            call.respond(ret)
+            return@get
+        }
 
-            get("/permissions") {
-                val resp = object {
-                    val permissions = mutableListOf<Permission>()
-                }
-                transaction {
-                    requireSuperuser(call.request)
-                    NexusPermissionEntity.all().map {
-                        resp.permissions.add(
-                            Permission(
-                                subjectType = it.subjectType,
-                                subjectId = it.subjectId,
-                                resourceType = it.resourceType,
-                                resourceId = it.resourceId,
-                                permissionName = it.permissionName,
-                            )
+        get("/permissions") {
+            val resp = object {
+                val permissions = mutableListOf<Permission>()
+            }
+            transaction {
+                requireSuperuser(call.request)
+                NexusPermissionEntity.all().map {
+                    resp.permissions.add(
+                        Permission(
+                            subjectType = it.subjectType,
+                            subjectId = it.subjectId,
+                            resourceType = it.resourceType,
+                            resourceId = it.resourceId,
+                            permissionName = it.permissionName,
                         )
-                    }
+                    )
                 }
-                call.respond(resp)
             }
+            call.respond(resp)
+        }
 
-            post("/permissions") {
-                val req = call.receive<ChangePermissionsRequest>()
-                val knownPermissions = listOf(
-                    "facade.talerwiregateway.history", 
"facade.talerwiregateway.transfer",
-                    "facade.anastasis.history"
+        post("/permissions") {
+            val req = call.receive<ChangePermissionsRequest>()
+            val knownPermissions = listOf(
+                "facade.talerwiregateway.history", 
"facade.talerwiregateway.transfer",
+                "facade.anastasis.history"
+            )
+            val permName = req.permission.permissionName.lowercase()
+            if (!knownPermissions.contains(permName)) {
+                throw NexusError(
+                    HttpStatusCode.BadRequest,
+                    "Permission $permName not known"
                 )
-                val permName = req.permission.permissionName.lowercase()
-                if (!knownPermissions.contains(permName)) {
-                    throw NexusError(
-                        HttpStatusCode.BadRequest,
-                        "Permission $permName not known"
-                    )
-                }
-                transaction {
-                    requireSuperuser(call.request)
-                    val existingPerm = findPermission(req.permission)
-                    when (req.action) {
-                        PermissionChangeAction.GRANT -> {
-                            if (existingPerm == null) {
-                                NexusPermissionEntity.new {
-                                    subjectType = req.permission.subjectType
-                                    subjectId = req.permission.subjectId
-                                    resourceType = req.permission.resourceType
-                                    resourceId = req.permission.resourceId
-                                    permissionName = permName
+            }
+            transaction {
+                requireSuperuser(call.request)
+                val existingPerm = findPermission(req.permission)
+                when (req.action) {
+                    PermissionChangeAction.GRANT -> {
+                        if (existingPerm == null) {
+                            NexusPermissionEntity.new {
+                                subjectType = req.permission.subjectType
+                                subjectId = req.permission.subjectId
+                                resourceType = req.permission.resourceType
+                                resourceId = req.permission.resourceId
+                                permissionName = permName
 
-                                }
                             }
                         }
-                        PermissionChangeAction.REVOKE -> {
-                            existingPerm?.delete()
-                        }
                     }
-                    null
-                }
-                call.respond(object {})
-            }
-
-            get("/users") {
-                transaction {
-                    requireSuperuser(call.request)
-                }
-                val users = transaction {
-                    transaction {
-                        NexusUserEntity.all().map {
-                            UserInfo(it.username, it.superuser)
-                        }
+                    PermissionChangeAction.REVOKE -> {
+                        existingPerm?.delete()
                     }
                 }
-                val usersResp = UsersResponse(users)
-                call.respond(usersResp)
-                return@get
+                null
             }
+            call.respond(object {})
+        }
 
-            // change a user's password
-            post("/users/{username}/password") {
-                val body = call.receiveJson<ChangeUserPassword>()
-                val targetUsername = ensureNonNull(call.parameters["username"])
-                transaction {
-                    requireSuperuser(call.request)
-                    val targetUser = NexusUserEntity.find {
-                        NexusUsersTable.username eq targetUsername
-                    }.firstOrNull()
-                    if (targetUser == null) throw NexusError(
-                        HttpStatusCode.NotFound,
-                        "Username $targetUsername not found"
-                    )
-                    targetUser.passwordHash = 
CryptoUtil.hashpw(body.newPassword)
-                }
-                call.respond(NexusMessage(message = "Password successfully 
changed"))
-                return@post
+        get("/users") {
+            transaction {
+                requireSuperuser(call.request)
             }
-
-            // Add a new ordinary user in the system (requires superuser 
privileges)
-            post("/users") {
-                val body = call.receiveJson<CreateUserRequest>()
-                val requestedUsername = requireValidResourceName(body.username)
+            val users = transaction {
                 transaction {
-                    requireSuperuser(call.request)
-                    // check if username is available
-                    val checkUsername = NexusUserEntity.find {
-                        NexusUsersTable.username eq requestedUsername
-                    }.firstOrNull()
-                    if (checkUsername != null) throw NexusError(
-                        HttpStatusCode.Conflict,
-                        "Username $requestedUsername unavailable"
-                    )
-                    NexusUserEntity.new {
-                        username = requestedUsername
-                        passwordHash = CryptoUtil.hashpw(body.password)
-                        superuser = false
+                    NexusUserEntity.all().map {
+                        UserInfo(it.username, it.superuser)
                     }
                 }
-                call.respond(
-                    NexusMessage(
-                        message = "New user '${body.username}' registered"
-                    )
-                )
-                return@post
             }
+            val usersResp = UsersResponse(users)
+            call.respond(usersResp)
+            return@get
+        }
 
-            get("/bank-connection-protocols") {
+        // change a user's password
+        post("/users/{username}/password") {
+            val body = call.receiveJson<ChangeUserPassword>()
+            val targetUsername = ensureNonNull(call.parameters["username"])
+            transaction {
                 requireSuperuser(call.request)
-                call.respond(
-                    HttpStatusCode.OK,
-                    BankProtocolsResponse(listOf("ebics", "loopback"))
+                val targetUser = NexusUserEntity.find {
+                    NexusUsersTable.username eq targetUsername
+                }.firstOrNull()
+                if (targetUser == null) throw NexusError(
+                    HttpStatusCode.NotFound,
+                    "Username $targetUsername not found"
                 )
-                return@get
+                targetUser.passwordHash = CryptoUtil.hashpw(body.newPassword)
             }
+            call.respond(NexusMessage(message = "Password successfully 
changed"))
+            return@post
+        }
 
-            route("/bank-connection-protocols/ebics") {
-                ebicsBankProtocolRoutes(client)
+        // Add a new ordinary user in the system (requires superuser 
privileges)
+        post("/users") {
+            val body = call.receiveJson<CreateUserRequest>()
+            val requestedUsername = requireValidResourceName(body.username)
+            transaction {
+                requireSuperuser(call.request)
+                // check if username is available
+                val checkUsername = NexusUserEntity.find {
+                    NexusUsersTable.username eq requestedUsername
+                }.firstOrNull()
+                if (checkUsername != null) throw NexusError(
+                    HttpStatusCode.Conflict,
+                    "Username $requestedUsername unavailable"
+                )
+                NexusUserEntity.new {
+                    username = requestedUsername
+                    passwordHash = CryptoUtil.hashpw(body.password)
+                    superuser = false
+                }
             }
+            call.respond(
+                NexusMessage(
+                    message = "New user '${body.username}' registered"
+                )
+            )
+            return@post
+        }
 
-            // Shows the bank accounts belonging to the requesting user.
-            get("/bank-accounts") {
-                val bankAccounts = BankAccounts()
-                transaction {
-                    authenticateRequest(call.request)
-                    // FIXME(dold): Only return accounts the user has at least 
read access to?
-                    NexusBankAccountEntity.all().forEach {
-                        bankAccounts.accounts.add(
-                            BankAccount(
-                                ownerName = it.accountHolder,
-                                iban = it.iban,
-                                bic = it.bankCode,
-                                nexusBankAccountId = it.bankAccountName
-                            )
+        get("/bank-connection-protocols") {
+            requireSuperuser(call.request)
+            call.respond(
+                HttpStatusCode.OK,
+                BankProtocolsResponse(listOf("ebics", "loopback"))
+            )
+            return@get
+        }
+
+        route("/bank-connection-protocols/ebics") {
+            ebicsBankProtocolRoutes(client)
+        }
+
+        // Shows the bank accounts belonging to the requesting user.
+        get("/bank-accounts") {
+            val bankAccounts = BankAccounts()
+            transaction {
+                authenticateRequest(call.request)
+                // FIXME(dold): Only return accounts the user has at least 
read access to?
+                NexusBankAccountEntity.all().forEach {
+                    bankAccounts.accounts.add(
+                        BankAccount(
+                            ownerName = it.accountHolder,
+                            iban = it.iban,
+                            bic = it.bankCode,
+                            nexusBankAccountId = it.bankAccountName
                         )
-                    }
+                    )
                 }
-                call.respond(bankAccounts)
-                return@get
             }
-            post("/bank-accounts/{accountId}/test-camt-ingestion/{type}") {
-                requireSuperuser(call.request)
-                processCamtMessage(
-                    ensureNonNull(call.parameters["accountId"]),
-                    XMLUtil.parseStringIntoDom(call.receiveText()),
-                    ensureNonNull(call.parameters["type"])
-                )
-                call.respond(object {})
-                return@post
-            }
-            get("/bank-accounts/{accountId}/schedule") {
-                requireSuperuser(call.request)
-                val resp = jacksonObjectMapper().createObjectNode()
-                val ops = jacksonObjectMapper().createObjectNode()
-                val accountId = ensureNonNull(call.parameters["accountId"])
-                resp.set<JsonNode>("schedule", ops)
-                transaction {
-                    NexusBankAccountEntity.findByName(accountId)
-                        ?: throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
-                    NexusScheduledTaskEntity.find {
-                        (NexusScheduledTasksTable.resourceType eq 
"bank-account") and
-                                (NexusScheduledTasksTable.resourceId eq 
accountId)
+            call.respond(bankAccounts)
+            return@get
+        }
+        post("/bank-accounts/{accountId}/test-camt-ingestion/{type}") {
+            requireSuperuser(call.request)
+            processCamtMessage(
+                ensureNonNull(call.parameters["accountId"]),
+                XMLUtil.parseStringIntoDom(call.receiveText()),
+                ensureNonNull(call.parameters["type"])
+            )
+            call.respond(object {})
+            return@post
+        }
+        get("/bank-accounts/{accountId}/schedule") {
+            requireSuperuser(call.request)
+            val resp = jacksonObjectMapper().createObjectNode()
+            val ops = jacksonObjectMapper().createObjectNode()
+            val accountId = ensureNonNull(call.parameters["accountId"])
+            resp.set<JsonNode>("schedule", ops)
+            transaction {
+                NexusBankAccountEntity.findByName(accountId)
+                    ?: throw NexusError(HttpStatusCode.NotFound, "unknown bank 
account")
+                NexusScheduledTaskEntity.find {
+                    (NexusScheduledTasksTable.resourceType eq "bank-account") 
and
+                            (NexusScheduledTasksTable.resourceId eq accountId)
 
-                    }.forEach {
-                        val t = jacksonObjectMapper().createObjectNode()
-                        ops.set<JsonNode>(it.taskName, t)
-                        t.put("cronspec", it.taskCronspec)
-                        t.put("type", it.taskType)
-                        t.set<JsonNode>("params", 
jacksonObjectMapper().readTree(it.taskParams))
-                    }
+                }.forEach {
+                    val t = jacksonObjectMapper().createObjectNode()
+                    ops.set<JsonNode>(it.taskName, t)
+                    t.put("cronspec", it.taskCronspec)
+                    t.put("type", it.taskType)
+                    t.set<JsonNode>("params", 
jacksonObjectMapper().readTree(it.taskParams))
                 }
-                call.respond(resp)
-                return@get
             }
+            call.respond(resp)
+            return@get
+        }
 
-            post("/bank-accounts/{accountId}/schedule") {
-                requireSuperuser(call.request)
-                val schedSpec = call.receive<CreateAccountTaskRequest>()
-                val accountId = ensureNonNull(call.parameters["accountId"])
-                transaction {
-                    authenticateRequest(call.request)
-                    NexusBankAccountEntity.findByName(accountId)
-                        ?: throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
-                    try {
-                        NexusCron.parser.parse(schedSpec.cronspec)
-                    } catch (e: IllegalArgumentException) {
-                        throw NexusError(HttpStatusCode.BadRequest, "bad cron 
spec: ${e.message}")
-                    }
-                    // sanity checks.
-                    when (schedSpec.type) {
-                        "fetch" -> {
-                            
jacksonObjectMapper().treeToValue(schedSpec.params, FetchSpecJson::class.java)
-                                ?: throw NexusError(HttpStatusCode.BadRequest, 
"bad fetch spec")
-                        }
-                        "submit" -> {
-                        }
-                        else -> throw NexusError(HttpStatusCode.BadRequest, 
"unsupported task type")
-                    }
-                    val oldSchedTask = NexusScheduledTaskEntity.find {
-                        (NexusScheduledTasksTable.taskName eq schedSpec.name) 
and
-                                (NexusScheduledTasksTable.resourceType eq 
"bank-account") and
-                                (NexusScheduledTasksTable.resourceId eq 
accountId)
-
-                    }.firstOrNull()
-                    if (oldSchedTask != null) {
-                        throw NexusError(HttpStatusCode.BadRequest, "schedule 
task already exists")
+        post("/bank-accounts/{accountId}/schedule") {
+            requireSuperuser(call.request)
+            val schedSpec = call.receive<CreateAccountTaskRequest>()
+            val accountId = ensureNonNull(call.parameters["accountId"])
+            transaction {
+                authenticateRequest(call.request)
+                NexusBankAccountEntity.findByName(accountId)
+                    ?: throw NexusError(HttpStatusCode.NotFound, "unknown bank 
account")
+                try {
+                    NexusCron.parser.parse(schedSpec.cronspec)
+                } catch (e: IllegalArgumentException) {
+                    throw NexusError(HttpStatusCode.BadRequest, "bad cron 
spec: ${e.message}")
+                }
+                // sanity checks.
+                when (schedSpec.type) {
+                    "fetch" -> {
+                        jacksonObjectMapper().treeToValue(schedSpec.params, 
FetchSpecJson::class.java)
+                            ?: throw NexusError(HttpStatusCode.BadRequest, 
"bad fetch spec")
                     }
-                    NexusScheduledTaskEntity.new {
-                        resourceType = "bank-account"
-                        resourceId = accountId
-                        this.taskCronspec = schedSpec.cronspec
-                        this.taskName = 
requireValidResourceName(schedSpec.name)
-                        this.taskType = schedSpec.type
-                        this.taskParams =
-                            
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(schedSpec.params)
+                    "submit" -> {
                     }
+                    else -> throw NexusError(HttpStatusCode.BadRequest, 
"unsupported task type")
                 }
-                call.respond(object {})
-                return@post
-            }
+                val oldSchedTask = NexusScheduledTaskEntity.find {
+                    (NexusScheduledTasksTable.taskName eq schedSpec.name) and
+                            (NexusScheduledTasksTable.resourceType eq 
"bank-account") and
+                            (NexusScheduledTasksTable.resourceId eq accountId)
 
-            get("/bank-accounts/{accountId}/schedule/{taskId}") {
-                requireSuperuser(call.request)
-                val taskId = ensureNonNull(call.parameters["taskId"])
-                val task = transaction {
-                    NexusScheduledTaskEntity.find {
-                        NexusScheduledTasksTable.taskName eq taskId
-                    }.firstOrNull()
+                }.firstOrNull()
+                if (oldSchedTask != null) {
+                    throw NexusError(HttpStatusCode.BadRequest, "schedule task 
already exists")
+                }
+                NexusScheduledTaskEntity.new {
+                    resourceType = "bank-account"
+                    resourceId = accountId
+                    this.taskCronspec = schedSpec.cronspec
+                    this.taskName = requireValidResourceName(schedSpec.name)
+                    this.taskType = schedSpec.type
+                    this.taskParams =
+                        
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(schedSpec.params)
                 }
-                if (task == null) throw NexusError(HttpStatusCode.NotFound, 
"Task ${taskId} wasn't found")
-                call.respond(
-                    AccountTask(
-                        resourceId = task.resourceId,
-                        resourceType = task.resourceType,
-                        taskName = task.taskName,
-                        taskCronspec = task.taskCronspec,
-                        taskType = task.taskType,
-                        taskParams = task.taskParams,
-                        nextScheduledExecutionSec = 
task.nextScheduledExecutionSec,
-                        prevScheduledExecutionSec = 
task.prevScheduledExecutionSec
-                    )
-                )
-                return@get
             }
+            call.respond(object {})
+            return@post
+        }
 
-            delete("/bank-accounts/{accountId}/schedule/{taskId}") {
-                requireSuperuser(call.request)
-                logger.info("schedule delete requested")
-                val accountId = ensureNonNull(call.parameters["accountId"])
-                val taskId = ensureNonNull(call.parameters["taskId"])
-                transaction {
-                    val bankAccount = 
NexusBankAccountEntity.findByName(accountId)
-                    if (bankAccount == null) {
-                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
-                    }
-                    val oldSchedTask = NexusScheduledTaskEntity.find {
-                        (NexusScheduledTasksTable.taskName eq taskId) and
-                                (NexusScheduledTasksTable.resourceType eq 
"bank-account") and
-                                (NexusScheduledTasksTable.resourceId eq 
accountId)
+        get("/bank-accounts/{accountId}/schedule/{taskId}") {
+            requireSuperuser(call.request)
+            val taskId = ensureNonNull(call.parameters["taskId"])
+            val task = transaction {
+                NexusScheduledTaskEntity.find {
+                    NexusScheduledTasksTable.taskName eq taskId
+                }.firstOrNull()
+            }
+            if (task == null) throw NexusError(HttpStatusCode.NotFound, "Task 
${taskId} wasn't found")
+            call.respond(
+                AccountTask(
+                    resourceId = task.resourceId,
+                    resourceType = task.resourceType,
+                    taskName = task.taskName,
+                    taskCronspec = task.taskCronspec,
+                    taskType = task.taskType,
+                    taskParams = task.taskParams,
+                    nextScheduledExecutionSec = task.nextScheduledExecutionSec,
+                    prevScheduledExecutionSec = task.prevScheduledExecutionSec
+                )
+            )
+            return@get
+        }
 
-                    }.firstOrNull()
-                    oldSchedTask?.delete()
+        delete("/bank-accounts/{accountId}/schedule/{taskId}") {
+            requireSuperuser(call.request)
+            logger.info("schedule delete requested")
+            val accountId = ensureNonNull(call.parameters["accountId"])
+            val taskId = ensureNonNull(call.parameters["taskId"])
+            transaction {
+                val bankAccount = NexusBankAccountEntity.findByName(accountId)
+                if (bankAccount == null) {
+                    throw NexusError(HttpStatusCode.NotFound, "unknown bank 
account")
                 }
-                call.respond(object {})
+                val oldSchedTask = NexusScheduledTaskEntity.find {
+                    (NexusScheduledTasksTable.taskName eq taskId) and
+                            (NexusScheduledTasksTable.resourceType eq 
"bank-account") and
+                            (NexusScheduledTasksTable.resourceId eq accountId)
+
+                }.firstOrNull()
+                oldSchedTask?.delete()
             }
+            call.respond(object {})
+        }
 
-            get("/bank-accounts/{accountid}") {
-                requireSuperuser(call.request)
-                val accountId = ensureNonNull(call.parameters["accountid"])
-                val res = transaction {
-                    val bankAccount = 
NexusBankAccountEntity.findByName(accountId)
-                    if (bankAccount == null) {
-                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
-                    }
-                    val holderEnc = 
URLEncoder.encode(bankAccount.accountHolder, "UTF-8")
-                    val lastSeenBalance = NexusBankBalanceEntity.find {
-                        NexusBankBalancesTable.bankAccount eq bankAccount.id
-                    }.lastOrNull()
-                    return@transaction makeJsonObject {
-                        prop("defaultBankConnection", 
bankAccount.defaultBankConnection?.id?.value)
-                        prop("accountPaytoUri", 
"payto://iban/${bankAccount.iban}?receiver-name=$holderEnc")
-                        prop(
-                            "lastSeenBalance",
-                            if (lastSeenBalance != null) {
-                                val sign = if 
(lastSeenBalance.creditDebitIndicator == "DBIT") "-" else ""
-                                "${sign}${lastSeenBalance.balance}"
-                            } else {
-                                "not downloaded from the bank yet"
-                            }
-                        )
-                    }
+        get("/bank-accounts/{accountid}") {
+            requireSuperuser(call.request)
+            val accountId = ensureNonNull(call.parameters["accountid"])
+            val res = transaction {
+                val bankAccount = NexusBankAccountEntity.findByName(accountId)
+                if (bankAccount == null) {
+                    throw NexusError(HttpStatusCode.NotFound, "unknown bank 
account")
+                }
+                val holderEnc = URLEncoder.encode(bankAccount.accountHolder, 
"UTF-8")
+                val lastSeenBalance = NexusBankBalanceEntity.find {
+                    NexusBankBalancesTable.bankAccount eq bankAccount.id
+                }.lastOrNull()
+                return@transaction makeJsonObject {
+                    prop("defaultBankConnection", 
bankAccount.defaultBankConnection?.id?.value)
+                    prop("accountPaytoUri", 
"payto://iban/${bankAccount.iban}?receiver-name=$holderEnc")
+                    prop(
+                        "lastSeenBalance",
+                        if (lastSeenBalance != null) {
+                            val sign = if 
(lastSeenBalance.creditDebitIndicator == "DBIT") "-" else ""
+                            "${sign}${lastSeenBalance.balance}"
+                        } else {
+                            "not downloaded from the bank yet"
+                        }
+                    )
                 }
-                call.respond(res)
             }
+            call.respond(res)
+        }
 
-            // Submit one particular payment to the bank.
-            
post("/bank-accounts/{accountid}/payment-initiations/{uuid}/submit") {
-                requireSuperuser(call.request)
-                val uuid = ensureLong(call.parameters["uuid"])
-                transaction {
-                    authenticateRequest(call.request)
-                }
-                submitPaymentInitiation(client, uuid)
-                call.respondText("Payment $uuid submitted")
-                return@post
+        // Submit one particular payment to the bank.
+        post("/bank-accounts/{accountid}/payment-initiations/{uuid}/submit") {
+            requireSuperuser(call.request)
+            val uuid = ensureLong(call.parameters["uuid"])
+            transaction {
+                authenticateRequest(call.request)
             }
+            submitPaymentInitiation(client, uuid)
+            call.respondText("Payment $uuid submitted")
+            return@post
+        }
 
-            post("/bank-accounts/{accountid}/submit-all-payment-initiations") {
-                requireSuperuser(call.request)
-                val accountId = ensureNonNull(call.parameters["accountid"])
-                transaction {
-                    authenticateRequest(call.request)
-                }
-                submitAllPaymentInitiations(client, accountId)
-                call.respond(object {})
-                return@post
+        post("/bank-accounts/{accountid}/submit-all-payment-initiations") {
+            requireSuperuser(call.request)
+            val accountId = ensureNonNull(call.parameters["accountid"])
+            transaction {
+                authenticateRequest(call.request)
             }
+            submitAllPaymentInitiations(client, accountId)
+            call.respond(object {})
+            return@post
+        }
 
-            get("/bank-accounts/{accountid}/payment-initiations") {
-                requireSuperuser(call.request)
-                val ret = InitiatedPayments()
-                transaction {
-                    val bankAccount = requireBankAccount(call, "accountid")
-                    PaymentInitiationEntity.find {
-                        PaymentInitiationsTable.bankAccount eq 
bankAccount.id.value
-                    }.forEach {
-                        val sd = it.submissionDate
-                        ret.initiatedPayments.add(
-                            PaymentStatus(
-                                status = it.confirmationTransaction?.status,
-                                paymentInitiationId = it.id.value.toString(),
-                                submitted = it.submitted,
-                                creditorIban = it.creditorIban,
-                                creditorName = it.creditorName,
-                                creditorBic = it.creditorBic,
-                                amount = "${it.currency}:${it.sum}",
-                                subject = it.subject,
-                                submissionDate = if (sd != null) {
-                                    importDateFromMillis(sd).toDashedDate()
-                                } else null,
-                                preparationDate = 
importDateFromMillis(it.preparationDate).toDashedDate()
-                            )
+        get("/bank-accounts/{accountid}/payment-initiations") {
+            requireSuperuser(call.request)
+            val ret = InitiatedPayments()
+            transaction {
+                val bankAccount = requireBankAccount(call, "accountid")
+                PaymentInitiationEntity.find {
+                    PaymentInitiationsTable.bankAccount eq bankAccount.id.value
+                }.forEach {
+                    val sd = it.submissionDate
+                    ret.initiatedPayments.add(
+                        PaymentStatus(
+                            status = it.confirmationTransaction?.status,
+                            paymentInitiationId = it.id.value.toString(),
+                            submitted = it.submitted,
+                            creditorIban = it.creditorIban,
+                            creditorName = it.creditorName,
+                            creditorBic = it.creditorBic,
+                            amount = "${it.currency}:${it.sum}",
+                            subject = it.subject,
+                            submissionDate = if (sd != null) {
+                                importDateFromMillis(sd).toDashedDate()
+                            } else null,
+                            preparationDate = 
importDateFromMillis(it.preparationDate).toDashedDate()
                         )
-                    }
+                    )
                 }
-                call.respond(ret)
-                return@get
             }
+            call.respond(ret)
+            return@get
+        }
 
-            // Shows information about one particular payment initiation.
-            get("/bank-accounts/{accountid}/payment-initiations/{uuid}") {
-                requireSuperuser(call.request)
-                val res = transaction {
-                    val paymentInitiation = 
getPaymentInitiation(ensureLong(call.parameters["uuid"]))
-                    return@transaction object {
-                        val paymentInitiation = paymentInitiation
-                        val paymentStatus = 
paymentInitiation.confirmationTransaction?.status
-                    }
+        // Shows information about one particular payment initiation.
+        get("/bank-accounts/{accountid}/payment-initiations/{uuid}") {
+            requireSuperuser(call.request)
+            val res = transaction {
+                val paymentInitiation = 
getPaymentInitiation(ensureLong(call.parameters["uuid"]))
+                return@transaction object {
+                    val paymentInitiation = paymentInitiation
+                    val paymentStatus = 
paymentInitiation.confirmationTransaction?.status
                 }
-                val sd = res.paymentInitiation.submissionDate
-                call.respond(
-                    PaymentStatus(
-                        paymentInitiationId = 
res.paymentInitiation.id.value.toString(),
-                        submitted = res.paymentInitiation.submitted,
-                        creditorName = res.paymentInitiation.creditorName,
-                        creditorBic = res.paymentInitiation.creditorBic,
-                        creditorIban = res.paymentInitiation.creditorIban,
-                        amount = 
"${res.paymentInitiation.currency}:${res.paymentInitiation.sum}",
-                        subject = res.paymentInitiation.subject,
-                        submissionDate = if (sd != null) {
-                            importDateFromMillis(sd).toDashedDate()
-                        } else null,
-                        status = res.paymentStatus,
-                        preparationDate = 
importDateFromMillis(res.paymentInitiation.preparationDate).toDashedDate()
-                    )
-                )
-                return@get
             }
+            val sd = res.paymentInitiation.submissionDate
+            call.respond(
+                PaymentStatus(
+                    paymentInitiationId = 
res.paymentInitiation.id.value.toString(),
+                    submitted = res.paymentInitiation.submitted,
+                    creditorName = res.paymentInitiation.creditorName,
+                    creditorBic = res.paymentInitiation.creditorBic,
+                    creditorIban = res.paymentInitiation.creditorIban,
+                    amount = 
"${res.paymentInitiation.currency}:${res.paymentInitiation.sum}",
+                    subject = res.paymentInitiation.subject,
+                    submissionDate = if (sd != null) {
+                        importDateFromMillis(sd).toDashedDate()
+                    } else null,
+                    status = res.paymentStatus,
+                    preparationDate = 
importDateFromMillis(res.paymentInitiation.preparationDate).toDashedDate()
+                )
+            )
+            return@get
+        }
 
-            delete("/bank-accounts/{accountId}/payment-initiations/{uuid}") {
-                requireSuperuser(call.request)
-                val uuid = ensureLong(call.parameters["uuid"])
-                transaction {
-                    val paymentInitiation = getPaymentInitiation(uuid)
-                    paymentInitiation.delete()
-                }
-                call.respond(NexusMessage(message = "Payment initiation $uuid 
deleted"))
+        delete("/bank-accounts/{accountId}/payment-initiations/{uuid}") {
+            requireSuperuser(call.request)
+            val uuid = ensureLong(call.parameters["uuid"])
+            transaction {
+                val paymentInitiation = getPaymentInitiation(uuid)
+                paymentInitiation.delete()
             }
+            call.respond(NexusMessage(message = "Payment initiation $uuid 
deleted"))
+        }
 
-            // Adds a new payment initiation.
-            post("/bank-accounts/{accountid}/payment-initiations") {
-                requireSuperuser(call.request)
-                val body = call.receive<CreatePaymentInitiationRequest>()
-                val accountId = ensureNonNull(call.parameters["accountid"])
-                if (!validateBic(body.bic)) {
-                    throw NexusError(HttpStatusCode.BadRequest, "invalid BIC 
(${body.bic})")
+        // Adds a new payment initiation.
+        post("/bank-accounts/{accountid}/payment-initiations") {
+            requireSuperuser(call.request)
+            val body = call.receive<CreatePaymentInitiationRequest>()
+            val accountId = ensureNonNull(call.parameters["accountid"])
+            if (!validateBic(body.bic)) {
+                throw NexusError(HttpStatusCode.BadRequest, "invalid BIC 
(${body.bic})")
+            }
+            val res = transaction {
+                authenticateRequest(call.request)
+                val bankAccount = NexusBankAccountEntity.findByName(accountId)
+                if (bankAccount == null) {
+                    throw NexusError(HttpStatusCode.NotFound, "unknown bank 
account ($accountId)")
                 }
-                val res = transaction {
-                    authenticateRequest(call.request)
-                    val bankAccount = 
NexusBankAccountEntity.findByName(accountId)
-                    if (bankAccount == null) {
-                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account ($accountId)")
-                    }
-                    val amount = parseAmount(body.amount)
-                    val paymentEntity = addPaymentInitiation(
-                        Pain001Data(
-                            creditorIban = body.iban,
-                            creditorBic = body.bic,
-                            creditorName = body.name,
-                            sum = amount.amount,
-                            currency = amount.currency,
-                            subject = body.subject
-                        ),
-                        bankAccount
-                    )
-                    return@transaction object {
-                        val uuid = paymentEntity.id.value
-                    }
+                val amount = parseAmount(body.amount)
+                val paymentEntity = addPaymentInitiation(
+                    Pain001Data(
+                        creditorIban = body.iban,
+                        creditorBic = body.bic,
+                        creditorName = body.name,
+                        sum = amount.amount,
+                        currency = amount.currency,
+                        subject = body.subject
+                    ),
+                    bankAccount
+                )
+                return@transaction object {
+                    val uuid = paymentEntity.id.value
                 }
-                call.respond(
-                    HttpStatusCode.OK,
-                    PaymentInitiationResponse(uuid = res.uuid.toString())
+            }
+            call.respond(
+                HttpStatusCode.OK,
+                PaymentInitiationResponse(uuid = res.uuid.toString())
+            )
+            return@post
+        }
+
+        // Downloads new transactions from the bank.
+        post("/bank-accounts/{accountid}/fetch-transactions") {
+            requireSuperuser(call.request)
+            val accountid = call.parameters["accountid"]
+            if (accountid == null) {
+                throw NexusError(
+                    HttpStatusCode.BadRequest,
+                    "Account id missing"
                 )
-                return@post
             }
+            val fetchSpec = if (call.request.hasBody()) {
+                call.receive<FetchSpecJson>()
+            } else {
+                FetchSpecLatestJson(
+                    FetchLevel.STATEMENT,
+                    null
+                )
+            }
+            val ingestionResult = fetchBankAccountTransactions(client, 
fetchSpec, accountid)
+            call.respond(ingestionResult)
+            return@post
+        }
 
-            // Downloads new transactions from the bank.
-            post("/bank-accounts/{accountid}/fetch-transactions") {
-                requireSuperuser(call.request)
-                val accountid = call.parameters["accountid"]
-                if (accountid == null) {
-                    throw NexusError(
-                        HttpStatusCode.BadRequest,
-                        "Account id missing"
-                    )
+        // Asks list of transactions ALREADY downloaded from the bank.
+        get("/bank-accounts/{accountid}/transactions") {
+            requireSuperuser(call.request)
+            val bankAccountId = expectNonNull(call.parameters["accountid"])
+            val ret = Transactions()
+            transaction {
+                authenticateRequest(call.request)
+                val bankAccount = 
NexusBankAccountEntity.findByName(bankAccountId)
+                if (bankAccount == null) {
+                    throw NexusError(HttpStatusCode.NotFound, "unknown bank 
account")
                 }
-                val fetchSpec = if (call.request.hasBody()) {
-                    call.receive<FetchSpecJson>()
-                } else {
-                    FetchSpecLatestJson(
-                        FetchLevel.STATEMENT,
-                        null
+                NexusBankTransactionEntity.find { 
NexusBankTransactionsTable.bankAccount eq bankAccount.id }.map {
+                    val tx = jacksonObjectMapper().readValue(
+                        it.transactionJson, CamtBankAccountEntry::class.java
                     )
+                    ret.transactions.add(tx)
                 }
-                val ingestionResult = fetchBankAccountTransactions(client, 
fetchSpec, accountid)
-                call.respond(ingestionResult)
-                return@post
             }
+            call.respond(ret)
+            return@get
+        }
 
-            // Asks list of transactions ALREADY downloaded from the bank.
-            get("/bank-accounts/{accountid}/transactions") {
-                requireSuperuser(call.request)
-                val bankAccountId = expectNonNull(call.parameters["accountid"])
-                val ret = Transactions()
-                transaction {
-                    authenticateRequest(call.request)
-                    val bankAccount = 
NexusBankAccountEntity.findByName(bankAccountId)
-                    if (bankAccount == null) {
-                        throw NexusError(HttpStatusCode.NotFound, "unknown 
bank account")
-                    }
-                    NexusBankTransactionEntity.find { 
NexusBankTransactionsTable.bankAccount eq bankAccount.id }.map {
-                        val tx = jacksonObjectMapper().readValue(
-                            it.transactionJson, 
CamtBankAccountEntry::class.java
-                        )
-                        ret.transactions.add(tx)
-                    }
+        // Adds a new bank transport.
+        post("/bank-connections") {
+            requireSuperuser(call.request)
+            // user exists and is authenticated.
+            val body = call.receive<CreateBankConnectionRequestJson>()
+            requireValidResourceName(body.name)
+            transaction {
+                val user = authenticateRequest(call.request)
+                val existingConn =
+                    NexusBankConnectionEntity.find { 
NexusBankConnectionsTable.connectionId eq body.name }
+                        .firstOrNull()
+                if (existingConn != null) {
+                    throw NexusError(HttpStatusCode.Conflict, "connection 
'${body.name}' exists already")
                 }
-                call.respond(ret)
-                return@get
-            }
-
-            // Adds a new bank transport.
-            post("/bank-connections") {
-                requireSuperuser(call.request)
-                // user exists and is authenticated.
-                val body = call.receive<CreateBankConnectionRequestJson>()
-                requireValidResourceName(body.name)
-                transaction {
-                    val user = authenticateRequest(call.request)
-                    val existingConn =
-                        NexusBankConnectionEntity.find { 
NexusBankConnectionsTable.connectionId eq body.name }
-                            .firstOrNull()
-                    if (existingConn != null) {
-                        throw NexusError(HttpStatusCode.Conflict, "connection 
'${body.name}' exists already")
-                    }
-                    when (body) {
-                        is CreateBankConnectionFromBackupRequestJson -> {
-                            val type = body.data.get("type")
-                            if (type == null || !type.isTextual) {
-                                throw NexusError(HttpStatusCode.BadRequest, 
"backup needs type")
-                            }
-                            val plugin = getConnectionPlugin(type.textValue())
-                            plugin.createConnectionFromBackup(body.name, user, 
body.passphrase, body.data)
-                        }
-                        is CreateBankConnectionFromNewRequestJson -> {
-                            val plugin = getConnectionPlugin(body.type)
-                            plugin.createConnection(body.name, user, body.data)
+                when (body) {
+                    is CreateBankConnectionFromBackupRequestJson -> {
+                        val type = body.data.get("type")
+                        if (type == null || !type.isTextual) {
+                            throw NexusError(HttpStatusCode.BadRequest, 
"backup needs type")
                         }
+                        val plugin = getConnectionPlugin(type.textValue())
+                        plugin.createConnectionFromBackup(body.name, user, 
body.passphrase, body.data)
+                    }
+                    is CreateBankConnectionFromNewRequestJson -> {
+                        val plugin = getConnectionPlugin(body.type)
+                        plugin.createConnection(body.name, user, body.data)
                     }
                 }
-                call.respond(object {})
             }
+            call.respond(object {})
+        }
 
-            post("/bank-connections/delete-connection") {
-                requireSuperuser(call.request)
-                val body = call.receive<BankConnectionDeletion>()
-                transaction {
-                    val conn =
-                        NexusBankConnectionEntity.find { 
NexusBankConnectionsTable.connectionId eq body.bankConnectionId }
-                            .firstOrNull() ?: throw NexusError(
-                            HttpStatusCode.NotFound,
-                            "Bank connection ${body.bankConnectionId}"
-                        )
-                    conn.delete() // temporary, and instead just _mark_ it as 
deleted?
-                }
-                call.respond(object {})
+        post("/bank-connections/delete-connection") {
+            requireSuperuser(call.request)
+            val body = call.receive<BankConnectionDeletion>()
+            transaction {
+                val conn =
+                    NexusBankConnectionEntity.find { 
NexusBankConnectionsTable.connectionId eq body.bankConnectionId }
+                        .firstOrNull() ?: throw NexusError(
+                        HttpStatusCode.NotFound,
+                        "Bank connection ${body.bankConnectionId}"
+                    )
+                conn.delete() // temporary, and instead just _mark_ it as 
deleted?
             }
+            call.respond(object {})
+        }
 
-            get("/bank-connections") {
-                requireSuperuser(call.request)
-                val connList = BankConnectionsList()
-                transaction {
-                    NexusBankConnectionEntity.all().forEach {
-                        connList.bankConnections.add(
-                            BankConnectionInfo(
-                                name = it.connectionId,
-                                type = it.type
-                            )
+        get("/bank-connections") {
+            requireSuperuser(call.request)
+            val connList = BankConnectionsList()
+            transaction {
+                NexusBankConnectionEntity.all().forEach {
+                    connList.bankConnections.add(
+                        BankConnectionInfo(
+                            name = it.connectionId,
+                            type = it.type
                         )
-                    }
+                    )
                 }
-                call.respond(connList)
             }
+            call.respond(connList)
+        }
 
-            get("/bank-connections/{connectionName}") {
-                requireSuperuser(call.request)
-                val resp = transaction {
-                    val conn = requireBankConnection(call, "connectionName")
-                    getConnectionPlugin(conn.type).getConnectionDetails(conn)
-                }
-                call.respond(resp)
+        get("/bank-connections/{connectionName}") {
+            requireSuperuser(call.request)
+            val resp = transaction {
+                val conn = requireBankConnection(call, "connectionName")
+                getConnectionPlugin(conn.type).getConnectionDetails(conn)
             }
+            call.respond(resp)
+        }
 
-            post("/bank-connections/{connectionName}/export-backup") {
-                requireSuperuser(call.request)
-                transaction { authenticateRequest(call.request) }
-                val body = call.receive<BackupRequestJson>()
-                val response = run {
-                    val conn = requireBankConnection(call, "connectionName")
-                    
getConnectionPlugin(conn.type).exportBackup(conn.connectionId, body.passphrase)
-                }
-                call.response.headers.append("Content-Disposition", 
"attachment")
-                call.respond(
-                    HttpStatusCode.OK,
-                    response
-                )
+        post("/bank-connections/{connectionName}/export-backup") {
+            requireSuperuser(call.request)
+            transaction { authenticateRequest(call.request) }
+            val body = call.receive<BackupRequestJson>()
+            val response = run {
+                val conn = requireBankConnection(call, "connectionName")
+                getConnectionPlugin(conn.type).exportBackup(conn.connectionId, 
body.passphrase)
             }
+            call.response.headers.append("Content-Disposition", "attachment")
+            call.respond(
+                HttpStatusCode.OK,
+                response
+            )
+        }
 
-            post("/bank-connections/{connectionName}/connect") {
-                requireSuperuser(call.request)
-                val conn = transaction {
-                    authenticateRequest(call.request)
-                    requireBankConnection(call, "connectionName")
-                }
-                val plugin = getConnectionPlugin(conn.type)
-                plugin.connect(client, conn.connectionId)
-                call.respond(NexusMessage(message = "Connection successful"))
+        post("/bank-connections/{connectionName}/connect") {
+            requireSuperuser(call.request)
+            val conn = transaction {
+                authenticateRequest(call.request)
+                requireBankConnection(call, "connectionName")
             }
+            val plugin = getConnectionPlugin(conn.type)
+            plugin.connect(client, conn.connectionId)
+            call.respond(NexusMessage(message = "Connection successful"))
+        }
 
-            get("/bank-connections/{connectionName}/keyletter") {
-                requireSuperuser(call.request)
-                val conn = transaction {
-                    authenticateRequest(call.request)
-                    requireBankConnection(call, "connectionName")
-                }
-                val pdfBytes = 
getConnectionPlugin(conn.type).exportAnalogDetails(conn)
-                call.respondBytes(pdfBytes, ContentType("application", "pdf"))
+        get("/bank-connections/{connectionName}/keyletter") {
+            requireSuperuser(call.request)
+            val conn = transaction {
+                authenticateRequest(call.request)
+                requireBankConnection(call, "connectionName")
             }
+            val pdfBytes = 
getConnectionPlugin(conn.type).exportAnalogDetails(conn)
+            call.respondBytes(pdfBytes, ContentType("application", "pdf"))
+        }
 
-            get("/bank-connections/{connectionName}/messages") {
-                requireSuperuser(call.request)
-                val ret = transaction {
-                    val list = BankMessageList()
-                    val conn = requireBankConnection(call, "connectionName")
-                    NexusBankMessageEntity.find { 
NexusBankMessagesTable.bankConnection eq conn.id }.map {
-                        list.bankMessages.add(
-                            BankMessageInfo(
-                                it.messageId,
-                                it.code,
-                                it.message.bytes.size.toLong()
-                            )
+        get("/bank-connections/{connectionName}/messages") {
+            requireSuperuser(call.request)
+            val ret = transaction {
+                val list = BankMessageList()
+                val conn = requireBankConnection(call, "connectionName")
+                NexusBankMessageEntity.find { 
NexusBankMessagesTable.bankConnection eq conn.id }.map {
+                    list.bankMessages.add(
+                        BankMessageInfo(
+                            it.messageId,
+                            it.code,
+                            it.message.bytes.size.toLong()
                         )
-                    }
-                    list
+                    )
                 }
-                call.respond(ret)
+                list
             }
+            call.respond(ret)
+        }
 
-            get("/bank-connections/{connid}/messages/{msgid}") {
-                requireSuperuser(call.request)
-                val ret = transaction {
-                    val msgid = call.parameters["msgid"]
-                    if (msgid == null || msgid == "") {
-                        throw NexusError(HttpStatusCode.BadRequest, "missing 
or invalid message ID")
-                    }
-                    val msg = NexusBankMessageEntity.find { 
NexusBankMessagesTable.messageId eq msgid }.firstOrNull()
-                        ?: throw NexusError(HttpStatusCode.NotFound, "bank 
message not found")
-                    return@transaction object {
-                        val msgContent = msg.message.bytes
-                    }
+        get("/bank-connections/{connid}/messages/{msgid}") {
+            requireSuperuser(call.request)
+            val ret = transaction {
+                val msgid = call.parameters["msgid"]
+                if (msgid == null || msgid == "") {
+                    throw NexusError(HttpStatusCode.BadRequest, "missing or 
invalid message ID")
+                }
+                val msg = NexusBankMessageEntity.find { 
NexusBankMessagesTable.messageId eq msgid }.firstOrNull()
+                    ?: throw NexusError(HttpStatusCode.NotFound, "bank message 
not found")
+                return@transaction object {
+                    val msgContent = msg.message.bytes
                 }
-                call.respondBytes(ret.msgContent, ContentType("application", 
"xml"))
             }
+            call.respondBytes(ret.msgContent, ContentType("application", 
"xml"))
+        }
 
-            get("/facades/{fcid}") {
-                requireSuperuser(call.request)
-                val fcid = ensureNonNull(call.parameters["fcid"])
-                val ret = transaction {
-                    val f = FacadeEntity.findByName(fcid) ?: throw NexusError(
-                        HttpStatusCode.NotFound, "Facade $fcid does not exist"
-                    )
-                    // FIXME: this only works for TWG urls.
-                    FacadeShowInfo(
-                        name = f.facadeName,
-                        type = f.type,
-                        baseUrl = call.url {
-                            parameters.clear()
-                            encodedPath = ""
-                            pathComponents("facades", f.facadeName, f.type)
-                            encodedPath += "/"
-                        },
-                        config = getFacadeState(f.type, f)
-                    )
-                }
-                call.respond(ret)
-                return@get
+        get("/facades/{fcid}") {
+            requireSuperuser(call.request)
+            val fcid = ensureNonNull(call.parameters["fcid"])
+            val ret = transaction {
+                val f = FacadeEntity.findByName(fcid) ?: throw NexusError(
+                    HttpStatusCode.NotFound, "Facade $fcid does not exist"
+                )
+                // FIXME: this only works for TWG urls.
+                FacadeShowInfo(
+                    name = f.facadeName,
+                    type = f.type,
+                    baseUrl = call.url {
+                        parameters.clear()
+                        encodedPath = ""
+                        pathComponents("facades", f.facadeName, f.type)
+                        encodedPath += "/"
+                    },
+                    config = getFacadeState(f.type, f)
+                )
             }
+            call.respond(ret)
+            return@get
+        }
 
-            get("/facades") {
-                requireSuperuser(call.request)
-                val ret = object {
-                    val facades = mutableListOf<FacadeShowInfo>()
-                }
-                transaction {
-                    val user = authenticateRequest(call.request)
-                    FacadeEntity.find {
-                        FacadesTable.creator eq user.id
-                    }.forEach {
-                        ret.facades.add(
-                            FacadeShowInfo(
-                                name = it.facadeName,
-                                type = it.type,
-                                baseUrl = call.url {
-                                    parameters.clear()
-                                    encodedPath = ""
-                                    pathComponents("facades", it.facadeName, 
it.type)
-                                    encodedPath += "/"
-                                },
-                                config = getFacadeState(it.type, it)
-                            )
+        get("/facades") {
+            requireSuperuser(call.request)
+            val ret = object {
+                val facades = mutableListOf<FacadeShowInfo>()
+            }
+            transaction {
+                val user = authenticateRequest(call.request)
+                FacadeEntity.find {
+                    FacadesTable.creator eq user.id
+                }.forEach {
+                    ret.facades.add(
+                        FacadeShowInfo(
+                            name = it.facadeName,
+                            type = it.type,
+                            baseUrl = call.url {
+                                parameters.clear()
+                                encodedPath = ""
+                                pathComponents("facades", it.facadeName, 
it.type)
+                                encodedPath += "/"
+                            },
+                            config = getFacadeState(it.type, it)
                         )
-                    }
+                    )
                 }
-                call.respond(ret)
-                return@get
             }
+            call.respond(ret)
+            return@get
+        }
 
-            delete("/facades/{fcid}") {
-                requireSuperuser(call.request)
-                val fcid = ensureNonNull(call.parameters["fcid"])
-                transaction {
-                    val f = FacadeEntity.findByName(fcid) ?: throw NexusError(
-                        HttpStatusCode.NotFound, "Facade $fcid does not exist"
-                    )
-                    f.delete()
-                }
-                call.respond({})
-                return@delete
+        delete("/facades/{fcid}") {
+            requireSuperuser(call.request)
+            val fcid = ensureNonNull(call.parameters["fcid"])
+            transaction {
+                val f = FacadeEntity.findByName(fcid) ?: throw NexusError(
+                    HttpStatusCode.NotFound, "Facade $fcid does not exist"
+                )
+                f.delete()
             }
+            call.respond({})
+            return@delete
+        }
 
-            post("/facades") {
-                requireSuperuser(call.request)
-                val body = call.receive<FacadeInfo>()
-                requireValidResourceName(body.name)
-                if (!listOf("taler-wire-gateway", 
"anastasis").contains(body.type))
-                 throw NexusError(
+        post("/facades") {
+            requireSuperuser(call.request)
+            val body = call.receive<FacadeInfo>()
+            requireValidResourceName(body.name)
+            if (!listOf("taler-wire-gateway", "anastasis").contains(body.type))
+                throw NexusError(
                     HttpStatusCode.NotImplemented,
                     "Facade type '${body.type}' is not implemented"
                 )
-                val newFacade = try {
-                    transaction {
-                        val user = authenticateRequest(call.request)
-                        FacadeEntity.new {
-                            facadeName = body.name
-                            type = body.type
-                            creator = user
-                        }
-                    }
-                } catch (e: ExposedSQLException) {
-                    logger.error("Could not persist facade name/type/creator: 
$e")
-                    throw NexusError(
-                        HttpStatusCode.BadRequest,
-                        "Server could not persist data, possibly due to 
unavailable facade name"
-                    )
-                }
+            val newFacade = try {
                 transaction {
-                    FacadeStateEntity.new {
-                        bankAccount = body.config.bankAccount
-                        bankConnection = body.config.bankConnection
-                        reserveTransferLevel = body.config.reserveTransferLevel
-                        facade = newFacade
-                        currency = body.config.currency
+                    val user = authenticateRequest(call.request)
+                    FacadeEntity.new {
+                        facadeName = body.name
+                        type = body.type
+                        creator = user
                     }
                 }
-                call.respondText("Facade created")
-                return@post
+            } catch (e: ExposedSQLException) {
+                logger.error("Could not persist facade name/type/creator: $e")
+                throw NexusError(
+                    HttpStatusCode.BadRequest,
+                    "Server could not persist data, possibly due to 
unavailable facade name"
+                )
             }
+            transaction {
+                FacadeStateEntity.new {
+                    bankAccount = body.config.bankAccount
+                    bankConnection = body.config.bankConnection
+                    reserveTransferLevel = body.config.reserveTransferLevel
+                    facade = newFacade
+                    currency = body.config.currency
+                }
+            }
+            call.respondText("Facade created")
+            return@post
+        }
 
-            route("/bank-connections/{connid}") {
+        route("/bank-connections/{connid}") {
 
-                // only ebics specific tasks under this part.
-                route("/ebics") {
-                    ebicsBankConnectionRoutes(client)
-                }
-                post("/fetch-accounts") {
-                    requireSuperuser(call.request)
-                    val conn = transaction {
-                        authenticateRequest(call.request)
-                        requireBankConnection(call, "connid")
-                    }
-                    getConnectionPlugin(conn.type).fetchAccounts(client, 
conn.connectionId)
-                    call.respond(object {})
+            // only ebics specific tasks under this part.
+            route("/ebics") {
+                ebicsBankConnectionRoutes(client)
+            }
+            post("/fetch-accounts") {
+                requireSuperuser(call.request)
+                val conn = transaction {
+                    authenticateRequest(call.request)
+                    requireBankConnection(call, "connid")
                 }
+                getConnectionPlugin(conn.type).fetchAccounts(client, 
conn.connectionId)
+                call.respond(object {})
+            }
 
-                // show all the offered accounts (both imported and non)
-                get("/accounts") {
-                    requireSuperuser(call.request)
-                    val ret = OfferedBankAccounts()
-                    transaction {
-                        val conn = requireBankConnection(call, "connid")
-                        OfferedBankAccountEntity.find {
-                            OfferedBankAccountsTable.bankConnection eq 
conn.id.value
-                        }.forEach { offeredAccount ->
-                            val importedId = offeredAccount.imported?.id
-                            val imported = if (importedId != null) {
-                                NexusBankAccountEntity.findById(importedId)
-                            } else {
-                                null
-                            }
-                            ret.accounts.add(
-                                OfferedBankAccount(
-                                    ownerName = offeredAccount.accountHolder,
-                                    iban = offeredAccount.iban,
-                                    bic = offeredAccount.bankCode,
-                                    offeredAccountId = 
offeredAccount.offeredAccountId,
-                                    nexusBankAccountId = 
imported?.bankAccountName
-                                )
-                            )
+            // show all the offered accounts (both imported and non)
+            get("/accounts") {
+                requireSuperuser(call.request)
+                val ret = OfferedBankAccounts()
+                transaction {
+                    val conn = requireBankConnection(call, "connid")
+                    OfferedBankAccountEntity.find {
+                        OfferedBankAccountsTable.bankConnection eq 
conn.id.value
+                    }.forEach { offeredAccount ->
+                        val importedId = offeredAccount.imported?.id
+                        val imported = if (importedId != null) {
+                            NexusBankAccountEntity.findById(importedId)
+                        } else {
+                            null
                         }
+                        ret.accounts.add(
+                            OfferedBankAccount(
+                                ownerName = offeredAccount.accountHolder,
+                                iban = offeredAccount.iban,
+                                bic = offeredAccount.bankCode,
+                                offeredAccountId = 
offeredAccount.offeredAccountId,
+                                nexusBankAccountId = imported?.bankAccountName
+                            )
+                        )
                     }
-                    call.respond(ret)
                 }
-
-                // import one account into libeufin.
-                post("/import-account") {
-                    requireSuperuser(call.request)
-                    val body = call.receive<ImportBankAccount>()
-                    importBankAccount(call, body.offeredAccountId, 
body.nexusBankAccountId)
-                    call.respond(object {})
-                }
-            }
-            route("/facades/{fcid}/taler-wire-gateway") {
-                talerFacadeRoutes(this)
-            }
-            route("/facades/{fcid}/anastasis") {
-                anastasisFacadeRoutes(this, client)
+                call.respond(ret)
             }
 
-            // Hello endpoint.
-            get("/") {
-                call.respondText("Hello, this is Nexus.\n")
-                return@get
+            // import one account into libeufin.
+            post("/import-account") {
+                requireSuperuser(call.request)
+                val body = call.receive<ImportBankAccount>()
+                importBankAccount(call, body.offeredAccountId, 
body.nexusBankAccountId)
+                call.respond(object {})
             }
         }
+        route("/facades/{fcid}/taler-wire-gateway") {
+            talerFacadeRoutes(this)
+        }
+        route("/facades/{fcid}/anastasis") {
+            anastasisFacadeRoutes(this, client)
+        }
+
+        // Hello endpoint.
+        get("/") {
+            call.respondText("Hello, this is Nexus.\n")
+            return@get
+        }
     }
+}
+fun serverMain(host: String, port: Int) {
+    val server = embeddedServer(Netty, port = port, host = host, module = 
nexusApp)
     logger.info("LibEuFin Nexus running on port $port")
     try {
         server.start(wait = true)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index fbde053..9411577 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -294,16 +294,15 @@ class Serve : CliktCommand("Run sandbox HTTP server") {
     )
     override fun run() {
         setLogLevel(logLevel)
-        val dbName = getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME)
+        execThrowableOrTerminate { 
dbCreateTables(getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME)) }
         if (withUnixSocket != null) {
-            execThrowableOrTerminate { dbCreateTables(dbName) }
             startServer(
                 withUnixSocket ?: throw Exception("Could not use the Unix 
domain socket path value!"),
                 app = sandboxApp
             )
             exitProcess(0)
         }
-        serverMain(dbName, port)
+        serverMain(port)
     }
 }
 
@@ -1088,8 +1087,7 @@ val sandboxApp: Application.() -> Unit = {
         }
     }
 }
-fun serverMain(dbName: String, port: Int) {
-    execThrowableOrTerminate { dbCreateTables(dbName) }
+fun serverMain(port: Int) {
     val server = embeddedServer(Netty, port = port, module = sandboxApp)
     logger.info("LibEuFin Sandbox running on port $port")
     try {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]