From 7a27b188de28a652f4b9e576a7ad01d6ae9a0038 Mon Sep 17 00:00:00 2001 From: Amir Rajabi Date: Wed, 7 Jan 2026 18:01:41 +0330 Subject: [PATCH 1/2] add admin services --- .../src/main/resources/application.yml | 2 + .../nilin/opex/api/core/spi/StorageProxy.kt | 10 ++ .../opex/controller/StorageAdminController.kt | 45 +++++++++ .../api/ports/proxy/impl/StorageProxyImpl.kt | 97 +++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/StorageProxy.kt create mode 100644 api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/StorageAdminController.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/StorageProxyImpl.kt diff --git a/api/api-app/src/main/resources/application.yml b/api/api-app/src/main/resources/application.yml index 63ec4437e..f45091932 100644 --- a/api/api-app/src/main/resources/application.yml +++ b/api/api-app/src/main/resources/application.yml @@ -125,6 +125,8 @@ app: url: http://opex-market opex-bc-gateway: url: http://opex-bc-gateway + storage: + url: http://storage auth: cert-url: http://keycloak:8080/realms/opex/protocol/openid-connect/certs iss-url: ${TOKEN_ISSUER_URL:http://keycloak:8080/realms/opex} diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/StorageProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/StorageProxy.kt new file mode 100644 index 000000000..01b52835c --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/StorageProxy.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.spi + +import org.springframework.http.ResponseEntity +import org.springframework.http.codec.multipart.FilePart + +interface StorageProxy { + suspend fun adminDownload(token: String, bucket: String, key: String): ResponseEntity + suspend fun adminUpload(token: String, bucket: String, key: String, file: FilePart,isPublic : Boolean? = false) + suspend fun adminDelete(token: String, bucket: String, key: String) +} diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/StorageAdminController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/StorageAdminController.kt new file mode 100644 index 000000000..7c8851018 --- /dev/null +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/StorageAdminController.kt @@ -0,0 +1,45 @@ +package co.nilin.opex.api.ports.opex.controller + +import co.nilin.opex.api.core.spi.StorageProxy +import co.nilin.opex.api.ports.opex.util.jwtAuthentication +import co.nilin.opex.api.ports.opex.util.tokenValue +import org.springframework.http.ResponseEntity +import org.springframework.http.codec.multipart.FilePart +import org.springframework.security.core.annotation.CurrentSecurityContext +import org.springframework.security.core.context.SecurityContext +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/opex/v1/admin/storage") +class StorageAdminController( + private val storageProxy: StorageProxy, +) { + @GetMapping + suspend fun download( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestParam("bucket") bucket: String, + @RequestParam("key") key: String, + ): ResponseEntity { + return storageProxy.adminDownload(securityContext.jwtAuthentication().tokenValue(), bucket, key) + } + + @PostMapping + suspend fun upload( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestParam("bucket") bucket: String, + @RequestParam("key") key: String, + @RequestPart("file") file: FilePart, + @RequestParam("isPublic") isPublic: Boolean? = false, + ) { + storageProxy.adminUpload(securityContext.jwtAuthentication().tokenValue(), bucket, key, file, isPublic) + } + + @DeleteMapping + suspend fun delete( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestParam("bucket") bucket: String, + @RequestParam("key") key: String, + ) { + storageProxy.adminDelete(securityContext.jwtAuthentication().tokenValue(), bucket, key) + } +} \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/StorageProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/StorageProxyImpl.kt new file mode 100644 index 000000000..e6bac8b9f --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/StorageProxyImpl.kt @@ -0,0 +1,97 @@ +package co.nilin.opex.api.ports.proxy.impl + +import co.nilin.opex.api.core.spi.StorageProxy +import co.nilin.opex.common.utils.LoggerDelegate +import kotlinx.coroutines.reactive.awaitSingle +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.http.codec.multipart.FilePart +import org.springframework.stereotype.Component +import org.springframework.util.LinkedMultiValueMap +import org.springframework.web.reactive.function.BodyInserters +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.awaitBodilessEntity +import reactor.core.publisher.Mono + +@Component +class StorageProxyImpl(@Qualifier("generalWebClient") private val webClient: WebClient) : StorageProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.storage.url}") + private lateinit var baseUrl: String + + override suspend fun adminDownload( + token: String, + bucket: String, + key: String + ): ResponseEntity { + return webClient.get() + .uri("$baseUrl/v2/admin") { + it.queryParam("bucket", bucket) + it.queryParam("key", key) + it.build() + } + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .accept( + MediaType.APPLICATION_OCTET_STREAM, + MediaType.APPLICATION_JSON + ) + .exchangeToMono { response -> + if (response.statusCode().isError) { + response.createException().flatMap { Mono.error(it) } + } else { + response.toEntity(ByteArray::class.java) + } + } + .awaitSingle() + } + + override suspend fun adminUpload( + token: String, + bucket: String, + key: String, + file: FilePart, + isPublic : Boolean? + ) { + webClient.post() + .uri("$baseUrl/v2/admin"){ + it.queryParam("isPublic", isPublic) + it.queryParam("bucket", bucket) + it.queryParam("key", key) + it.build() + } + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .contentType(MediaType.MULTIPART_FORM_DATA) + .body( + BodyInserters.fromMultipartData( + LinkedMultiValueMap().apply { + add("file", file) + } + )) + .retrieve() + .onStatus({ it.isError }) { it.createException() } + .awaitBodilessEntity() + } + + override suspend fun adminDelete( + token: String, + bucket: String, + key: String + ) { + webClient.delete() + .uri("$baseUrl/v2/admin") { + it.queryParam("bucket", bucket) + it.queryParam("key", key) + it.build() + } + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ it.isError }) { it.createException() } + .awaitBodilessEntity() + } +} From b5eccd827e78c02471f272c9855801565c01e099 Mon Sep 17 00:00:00 2001 From: Amir Rajabi Date: Tue, 27 Jan 2026 18:01:07 +0330 Subject: [PATCH 2/2] add new method for wallets backup --- docker-compose-otc.yml | 4 +- docker-compose.yml | 4 +- wallet/pom.xml | 6 + wallet/wallet-app/pom.xml | 4 + .../opex/wallet/app/service/BackupService.kt | 140 ++++++++++-------- .../src/main/resources/application-otc.yml | 9 +- .../src/main/resources/application.yml | 11 +- .../src/test/resources/application.yml | 2 + .../opex/wallet/core/spi/StorageProxy.kt | 7 + .../wallet-storage-proxy/.gitignore | 33 +++++ .../wallet-ports/wallet-storage-proxy/pom.xml | 42 ++++++ .../proxy/storage/impl/StorageProxyImpl.kt | 46 ++++++ .../src/main/resources/application.properties | 1 + 13 files changed, 243 insertions(+), 66 deletions(-) create mode 100644 wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/StorageProxy.kt create mode 100644 wallet/wallet-ports/wallet-storage-proxy/.gitignore create mode 100644 wallet/wallet-ports/wallet-storage-proxy/pom.xml create mode 100644 wallet/wallet-ports/wallet-storage-proxy/src/main/kotlin/co/nilin/opex/wallet/ports/proxy/storage/impl/StorageProxyImpl.kt create mode 100644 wallet/wallet-ports/wallet-storage-proxy/src/main/resources/application.properties diff --git a/docker-compose-otc.yml b/docker-compose-otc.yml index 8281f8fce..296198477 100644 --- a/docker-compose-otc.yml +++ b/docker-compose-otc.yml @@ -30,7 +30,9 @@ services: - VAULT_HOST=vault - SWAGGER_AUTH_URL=$KEYCLOAK_FRONTEND_URL - DRIVE_FOLDER_ID=$DRIVE_FOLDER_ID - - BACKUP_ENABLED=$WALLET_BACKUP_ENABLED + - STORAGE_FOLDER_ID=$STORAGE_FOLDER_ID + - BACKUP_ENABLED_STORAGE=$WALLET_BACKUP_ENABLED_STORAGE + - BACKUP_ENABLED_GOOGLE_DRIVE=$WALLET_BACKUP_ENABLED_GOOGLE_DRIVE - SPRING_PROFILES_ACTIVE=otc - AUTH_URL=${AUTH_URL} - AUTH_JWK_ENDPOINT=${JWK_ENDPOINT} diff --git a/docker-compose.yml b/docker-compose.yml index fb352af93..ba6590e56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -428,7 +428,9 @@ services: - VAULT_HOST=vault - SWAGGER_AUTH_URL=$KEYCLOAK_FRONTEND_URL - DRIVE_FOLDER_ID=$DRIVE_FOLDER_ID - - BACKUP_ENABLED=$WALLET_BACKUP_ENABLED + - STORAGE_FOLDER_ID=$STORAGE_FOLDER_ID + - BACKUP_ENABLED_STORAGE=$WALLET_BACKUP_ENABLED_STORAGE + - BACKUP_ENABLED_GOOGLE_DRIVE=$WALLET_BACKUP_ENABLED_GOOGLE_DRIVE - SYMBOLS=BTC_USDT,ETH_USDT,BTC_IRT,ETH_IRT,USDT_IRT,ETH_BUSD,BTC_BUSD,BNB_BUSD - WITHDRAW_LIMIT_ENABLED=${WITHDRAW_LIMIT_ENABLED} - WITHDRAW_OTP_REQUIRED_COUNT=${WITHDRAW_OTP_REQUIRED_COUNT} diff --git a/wallet/pom.xml b/wallet/pom.xml index 532e374f5..6db66a718 100644 --- a/wallet/pom.xml +++ b/wallet/pom.xml @@ -22,6 +22,7 @@ wallet-ports/wallet-eventlistener-kafka wallet-ports/wallet-bcgateway-proxy wallet-ports/wallet-profile-proxy + wallet-ports/wallet-storage-proxy wallet-ports/wallet-auth-proxy wallet-ports/wallet-accountant-proxy wallet-ports/wallet-otp-proxy @@ -60,6 +61,11 @@ wallet-profile-proxy ${project.version} + + co.nilin.opex.wallet.ports.storage + wallet-storage-proxy + ${project.version} + co.nilin.opex.wallet.ports.auth wallet-auth-proxy diff --git a/wallet/wallet-app/pom.xml b/wallet/wallet-app/pom.xml index ece5da778..9e1e23288 100644 --- a/wallet/wallet-app/pom.xml +++ b/wallet/wallet-app/pom.xml @@ -103,6 +103,10 @@ co.nilin.opex.wallet.ports.profile wallet-profile-proxy + + co.nilin.opex.wallet.ports.storage + wallet-storage-proxy + co.nilin.opex.wallet.ports.auth wallet-auth-proxy diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/BackupService.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/BackupService.kt index 02c7b121c..17ad6ad25 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/BackupService.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/BackupService.kt @@ -2,6 +2,7 @@ package co.nilin.opex.wallet.app.service import co.nilin.opex.wallet.core.model.BriefWallet import co.nilin.opex.wallet.core.model.WalletOwner +import co.nilin.opex.wallet.core.spi.StorageProxy import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import com.fasterxml.jackson.databind.ObjectMapper @@ -12,17 +13,18 @@ import com.google.api.services.drive.Drive import com.google.api.services.drive.DriveScopes import com.google.auth.http.HttpCredentialsAdapter import com.google.auth.oauth2.GoogleCredentials -import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Profile +import org.springframework.http.MediaType import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import java.io.File import java.nio.file.Files -import java.time.LocalDateTime -import java.util.concurrent.Executors +import java.time.Instant import com.google.api.services.drive.model.File as GoogleFile @Service @@ -30,70 +32,90 @@ import com.google.api.services.drive.model.File as GoogleFile class BackupService( private val walletManager: WalletManager, private val walletOwnerManager: WalletOwnerManager, - @Value("\${app.backup.enabled}") - private val isBackupEnabled: Boolean, - @Value("\${app.backup.drive.folder}") - private val folderId: String + private val storageProxy: StorageProxy, + @Value("\${app.backup.storage.enabled}") private val storageEnabled: Boolean, + @Value("\${app.backup.google-drive.enabled}") private val driveEnabled: Boolean, + @Value("\${app.backup.storage.folder}") private val storageFolderId: String, + @Value("\${app.backup.google-drive.folder}") private val driveFolderId: String ) { - private val logger = LoggerFactory.getLogger(BackupService::class.java) - private val dispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() - private val mapper = ObjectMapper() - - data class OwnerAndWallets(val owner: WalletOwner, val wallets: List) - - @Scheduled(initialDelay = 60000, fixedDelay = 1000 * 60 * 10) - private fun backup() { - if (!isBackupEnabled) return - runBlocking(dispatcher) { - try { - val data = arrayListOf() - - logger.info("Fetching wallets for backup...") - walletOwnerManager.findAllWalletOwners().forEach { - data.add(OwnerAndWallets(it, walletManager.findAllWalletsBriefNotZero(it.id!!))) - } - - logger.info("Writing wallets to temp file") - val file = writeWalletsToFile(data) - upload(file, file.name, folderId) - } catch (e: Exception) { - logger.error("Could not upload file to google drive", e) - } - } - } + private val logger = LoggerFactory.getLogger(javaClass) + private val mapper = ObjectMapper().findAndRegisterModules() - private fun writeWalletsToFile(wallets: List): File { - val fileName = LocalDateTime.now().toString() - val tempFile = Files.createTempFile(fileName, ".json").toFile() - mapper.writeValue(tempFile, wallets) - return tempFile - } + data class OwnerAndWallets( + val owner: WalletOwner, + val wallets: List + ) + + @Scheduled(initialDelay = 60_000, fixedDelay = 10 * 60 * 1000) + fun backup() = runBlocking { + if (!storageEnabled && !driveEnabled) return@runBlocking + + val fileName = "wallets-${Instant.now().toEpochMilli()}.json" + logger.info("Starting wallet backup: $fileName") - private fun upload(file: File, fileName: String, folderId: String) { try { - logger.info("Start upload process with folderId:$folderId") - val authFile = File("/drive-key.json") - val credentials = GoogleCredentials.fromStream(authFile.inputStream()).createScoped(DriveScopes.DRIVE) - val requestInitializer = HttpCredentialsAdapter(credentials) - - val service = Drive.Builder(NetHttpTransport(), GsonFactory.getDefaultInstance(), requestInitializer) - .setApplicationName("Wallet backup") - .build() - - val metadata = GoogleFile().apply { - name = fileName - parents = listOf(folderId) + val tempFile = writeBackupToTempFile() + + if (storageEnabled) { + storageProxy.systemUploadFile( + bucket = storageFolderId, + key = fileName, + file = tempFile, + ) } - val content = FileContent("text/*", file) - val uploaded = service.files().create(metadata, content) - .setFields("id,name") - .execute() - logger.info("File uploaded: ${uploaded.id}--${uploaded.name}") + if (driveEnabled) { + uploadToGoogleDrive(tempFile, fileName) + } + + tempFile.delete() } catch (e: Exception) { - logger.error("Wallet backup is enabled but could not upload to Google Drive", e) + logger.error("Wallet backup failed", e) } } -} \ No newline at end of file + private suspend fun writeBackupToTempFile(): File = + withContext(Dispatchers.IO) { + val file = Files.createTempFile("wallets-", ".json").toFile() + val data = walletOwnerManager.findAllWalletOwners().map { owner -> + OwnerAndWallets( + owner, + walletManager.findAllWalletsBriefNotZero(owner.id!!) + ) + } + mapper.writeValue(file, data) + file + } + + private fun uploadToGoogleDrive(file: File, fileName: String) { + logger.info("Uploading backup to Google Drive") + + val credentials = GoogleCredentials + .fromStream(File("/drive-key.json").inputStream()) + .createScoped(DriveScopes.DRIVE) + + val drive = Drive.Builder( + NetHttpTransport(), + GsonFactory.getDefaultInstance(), + HttpCredentialsAdapter(credentials) + ) + .setApplicationName("Wallet backup") + .build() + + val metadata = GoogleFile().apply { + name = fileName + parents = listOf(driveFolderId) + } + + drive.files().create( + metadata, + FileContent(MediaType.APPLICATION_JSON_VALUE, file) + ) + .setFields("id,name") + .execute() + .also { + logger.info("Uploaded to Drive: ${it.id}") + } + } +} diff --git a/wallet/wallet-app/src/main/resources/application-otc.yml b/wallet/wallet-app/src/main/resources/application-otc.yml index bf489d739..ffd08c5c9 100644 --- a/wallet/wallet-app/src/main/resources/application-otc.yml +++ b/wallet/wallet-app/src/main/resources/application-otc.yml @@ -88,8 +88,11 @@ app: system: uuid: 1 backup: - enabled: ${BACKUP_ENABLED:false} - drive: + storage: + enabled: ${BACKUP_ENABLED_STORAGE:false} + folder: ${STORAGE_FOLDER_ID:-} + google-drive: + enabled: ${BACKUP_ENABLED_GOOGLE_DRIVE:false} folder: ${DRIVE_FOLDER_ID:-} bc-gateway: url: http://bc-gateway:8080 @@ -101,6 +104,8 @@ app: url: http://opex-otp:8080/v1 profile: url: http://profile:8080 + storage: + url: http://storage:8080 reserved-transfer: life-time: 15 #minutes zone-offset: +03:30 diff --git a/wallet/wallet-app/src/main/resources/application.yml b/wallet/wallet-app/src/main/resources/application.yml index 4f12fa225..8c4c6c27a 100644 --- a/wallet/wallet-app/src/main/resources/application.yml +++ b/wallet/wallet-app/src/main/resources/application.yml @@ -36,7 +36,7 @@ spring: max-size: 20 max-idle-time: 60s validation-query: SELECT 1 - # initialization-mode: always + # initialization-mode: always datasource: url: jdbc:postgresql://${DB_IP_PORT:localhost}/opex username: ${dbusername:opex} @@ -131,8 +131,11 @@ app: system: uuid: 1 backup: - enabled: ${BACKUP_ENABLED:false} - drive: + storage: + enabled: ${BACKUP_ENABLED_STORAGE:false} + folder: ${STORAGE_FOLDER_ID:-} + google-drive: + enabled: ${BACKUP_ENABLED_GOOGLE_DRIVE:false} folder: ${DRIVE_FOLDER_ID:-} bc-gateway: url: lb://opex-bc-gateway @@ -144,6 +147,8 @@ app: url: lb://opex-otp/v1 profile: url: lb://opex-profile + storage: + url: lb://storage reserved-transfer: life-time: 15 #minutes symbols: ${SYMBOLS} diff --git a/wallet/wallet-app/src/test/resources/application.yml b/wallet/wallet-app/src/test/resources/application.yml index 48f868180..452b68c28 100644 --- a/wallet/wallet-app/src/test/resources/application.yml +++ b/wallet/wallet-app/src/test/resources/application.yml @@ -61,6 +61,8 @@ app: url: http://opex-otp:8080/v1 profile: url: http://profile:8080 + storage: + url: http://storage:8080 system: uuid: 1 reserved-transfer: diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/StorageProxy.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/StorageProxy.kt new file mode 100644 index 000000000..ba73aac30 --- /dev/null +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/StorageProxy.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.wallet.core.spi + +import java.io.File + +interface StorageProxy { + suspend fun systemUploadFile(bucket: String, key: String, file: File, isPublic: Boolean? = false) +} diff --git a/wallet/wallet-ports/wallet-storage-proxy/.gitignore b/wallet/wallet-ports/wallet-storage-proxy/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/wallet/wallet-ports/wallet-storage-proxy/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/wallet/wallet-ports/wallet-storage-proxy/pom.xml b/wallet/wallet-ports/wallet-storage-proxy/pom.xml new file mode 100644 index 000000000..67cb0bd92 --- /dev/null +++ b/wallet/wallet-ports/wallet-storage-proxy/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + co.nilin.opex.wallet + wallet + 1.0.1-beta.7 + ../../pom.xml + + co.nilin.opex.wallet.ports.storage + wallet-storage-proxy + wallet-storage-proxy + wallet-storage-proxy + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib + + + + org.springframework.boot + spring-boot-starter-test + test + + + + co.nilin.opex.wallet.core + wallet-core + + + + diff --git a/wallet/wallet-ports/wallet-storage-proxy/src/main/kotlin/co/nilin/opex/wallet/ports/proxy/storage/impl/StorageProxyImpl.kt b/wallet/wallet-ports/wallet-storage-proxy/src/main/kotlin/co/nilin/opex/wallet/ports/proxy/storage/impl/StorageProxyImpl.kt new file mode 100644 index 000000000..b22bc76d6 --- /dev/null +++ b/wallet/wallet-ports/wallet-storage-proxy/src/main/kotlin/co/nilin/opex/wallet/ports/proxy/storage/impl/StorageProxyImpl.kt @@ -0,0 +1,46 @@ +package co.nilin.opex.wallet.ports.proxy.storage.impl + +import co.nilin.opex.wallet.core.spi.StorageProxy +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.io.FileSystemResource +import org.springframework.http.MediaType +import org.springframework.http.client.MultipartBodyBuilder +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.BodyInserters +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.awaitBodilessEntity +import java.io.File + +@Component +class StorageProxyImpl(private val webClient: WebClient) : StorageProxy { + + @Value("\${app.storage.url}") + private lateinit var baseUrl: String + + override suspend fun systemUploadFile( + bucket: String, + key: String, + file: File, + isPublic: Boolean? + ) { + val bodyBuilder = MultipartBodyBuilder() + + bodyBuilder.part("file", FileSystemResource(file)) + .filename(file.name) + .contentType(MediaType.APPLICATION_JSON) + + webClient.post() + .uri("$baseUrl/v2/internal") { + it.queryParam("bucket", bucket) + it.queryParam("key", key) + it.queryParam("isPublic", isPublic) + it.build() + } + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(bodyBuilder.build())) + .retrieve() + .onStatus({ it.isError }) { it.createException() } + .awaitBodilessEntity() + } + +} diff --git a/wallet/wallet-ports/wallet-storage-proxy/src/main/resources/application.properties b/wallet/wallet-ports/wallet-storage-proxy/src/main/resources/application.properties new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/wallet/wallet-ports/wallet-storage-proxy/src/main/resources/application.properties @@ -0,0 +1 @@ +