libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

SyncApi.kt (3971B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2025 Taler Systems S.A.
      4 
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9 
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14 
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 package tech.libeufin.ebisync.api
     21 
     22 import kotlinx.serialization.Serializable
     23 import tech.libeufin.common.*
     24 import tech.libeufin.common.api.*
     25 import tech.libeufin.ebics.*
     26 import tech.libeufin.ebisync.*
     27 import tech.libeufin.ebisync.db.Database
     28 import io.ktor.server.application.*
     29 import io.ktor.server.request.*
     30 import io.ktor.server.response.*
     31 import io.ktor.server.routing.*
     32 import io.ktor.server.http.content.*
     33 import io.ktor.http.content.*
     34 import io.ktor.http.*
     35 import io.ktor.utils.io.*
     36 import java.nio.file.Path
     37 import tech.libeufin.common.VERSION
     38 
     39 @Serializable
     40 class TalerEbiSyncConfig() {
     41     val name: String = "taler-ebisync"
     42     val version: String = "0:0:0"
     43     val spa_version: String = VERSION
     44 }
     45 
     46 @Serializable
     47 data class ListSubmitOrders(
     48     val orders: List<SubmitOrder>
     49 )
     50 
     51 @Serializable
     52 data class SubmitOrder(
     53     val id: String,
     54     val description: String
     55 )
     56 
     57 @Serializable
     58 data class SyncSubmit(
     59     val order: String
     60 )
     61 
     62 fun Routing.syncApi(auth: AuthMethod, client: EbicsClient, spa: Path) {
     63     suspend fun orders() = client.download(EbicsOrder.V3.HKD) { stream ->
     64         val hkd = EbicsAdministrative.parseHKD(stream)
     65         hkd.partner.orders
     66             .filter { it.order.isUpload() }
     67     }
     68 
     69     get("/config") {
     70         call.respond(TalerEbiSyncConfig())
     71     }
     72     apiAuth(auth) {
     73         get("/") {
     74             call.respondRedirect("/webui/")
     75         }
     76         staticFiles("/webui/", spa.toFile())
     77         get("/submit") {
     78             call.respond(ListSubmitOrders(orders().map { SubmitOrder(it.order.description(), it.description) }))
     79         }
     80         post("/submit") {
     81             call.attributes.set(BODY_LIMIT, 10 * 1024 * 1024)
     82             val multipart = call.receiveMultipart()
     83             var orderId: String? = null
     84             var xml: ByteArray? = null
     85             
     86             multipart.forEachPart { part ->
     87                 when (part) {
     88                     is PartData.FormItem -> {
     89                         if (part.name == "order") {
     90                             orderId = part.value
     91                         }
     92                     }
     93                     is PartData.FileItem -> {
     94                         xml = part.provider().toByteArray()
     95                     }
     96                     else -> {}
     97                 }
     98                 part.dispose()
     99             }
    100 
    101             if (xml == null) {
    102                 throw badRequest("Missing file", TalerErrorCode.GENERIC_PARAMETER_MISSING) 
    103             } else if (orderId == null) {
    104                 throw badRequest("Missing orderId", TalerErrorCode.GENERIC_PARAMETER_MISSING)
    105             }
    106             val match = orders().find { it.order.description() == orderId } ?: throw notFound(
    107                 "Unknown order '$orderId'", TalerErrorCode.END
    108             )
    109 
    110             val order = try {
    111                 client.upload(match.order, xml)
    112             } catch (e: Exception) {
    113                 if (e is EbicsError.Code) {
    114                     throw conflict(e.fmt(), TalerErrorCode.END)
    115                 } else if (e is EbicsError) {
    116                     throw badGateway(e.fmt(), TalerErrorCode.END)
    117                 } else {
    118                     throw e
    119                 }
    120             }
    121             call.respond(SyncSubmit(order))
    122         }
    123     }
    124 }