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 }