diff --git a/CHANGELOG.md b/CHANGELOG.md index f76c0e4..9db43cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2.5.0 (2021-05-23) +## 2.5.0 (2021-06-12) - Support for Attachment APIs diff --git a/README.md b/README.md index f7fd889..36df9c8 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ repositories { ```groovy dependencies { /* ... */ - implementation 'org.jraf:klibqonto:2.4.0' + implementation 'org.jraf:klibqonto:2.5.0' } ``` diff --git a/build.gradle.kts b/build.gradle.kts index ca60a71..0561016 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ allprojects { } group = "org.jraf" - version = "2.4.0" + version = "2.5.0" // Show a report in the log when running tests tasks.withType { diff --git a/buildSrc/src/main/kotlin/Globals.kt b/buildSrc/src/main/kotlin/Globals.kt index 5436c6d..b143263 100644 --- a/buildSrc/src/main/kotlin/Globals.kt +++ b/buildSrc/src/main/kotlin/Globals.kt @@ -1,14 +1,14 @@ object Versions { // Misc and plugins const val GRADLE = "7.0.2" - const val KOTLIN = "1.5.0" - const val BEN_MANES_VERSIONS_PLUGIN = "0.38.0" + const val KOTLIN = "1.5.10" + const val BEN_MANES_VERSIONS_PLUGIN = "0.39.0" const val ANDROID_GRADLE_PLUGIN = "4.2.1" const val DOKKA_PLUGIN = "1.4.32" // Lib dependencies - const val KOTLIN_SERIALIZATION = "1.5.0" - const val KTOR = "1.5.4" + const val KOTLIN_SERIALIZATION = KOTLIN + const val KTOR = "1.6.0" const val COROUTINES = "1.5.0" const val SLF4J = "1.7.30" diff --git a/gradle.properties b/gradle.properties index 0d08ccc..cef1d0b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx4g +org.gradle.jvmargs=-Xmx4g --add-opens=java.base/java.io=ALL-UNNAMED # Use the daemon org.gradle.daemon=true diff --git a/library/src/androidMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt b/library/src/androidMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt new file mode 100644 index 0000000..e99a840 --- /dev/null +++ b/library/src/androidMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt @@ -0,0 +1,40 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2021-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments.file + +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import java.io.File + +actual class FileAttachmentByteInput actual constructor(filePath: String) : AttachmentByteInput { + private val inputStream by lazy { File(filePath).inputStream() } + + override fun read(byteArray: ByteArray): Int { + return inputStream.read(byteArray, 0, byteArray.size) + } + + override fun close() { + inputStream.close() + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/QontoClient.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/QontoClient.kt index daa0635..3fa4e16 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/QontoClient.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/QontoClient.kt @@ -26,6 +26,8 @@ package org.jraf.klibqonto.client import org.jraf.klibqonto.internal.client.QontoClientImpl import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -54,11 +56,7 @@ interface QontoClient { */ fun getLoginUri( oAuthCredentials: OAuthCredentials, - scopes: List = listOf( - OAuthScope.OFFLINE_ACCESS, - OAuthScope.ORGANIZATION_READ, - OAuthScope.OPENID, - ), + scopes: List = OAuthScope.values().toList(), uniqueState: String, ): String @@ -240,6 +238,38 @@ interface QontoClient { * See also [the API documentation](https://api-doc.qonto.com/docs/business-api/reference/openapi_v2.yml/paths/~1v2~1transactions~1%7Btransaction_id%7D~1attachments/get) */ suspend fun getAttachmentList(transactionInternalId: String): List + + /** + * Add an attachment to a transaction. + * + * @param transactionInternalId The internal id of the Transaction - e.g. `4c306508-dac9-410b-9937-e87b02462e42` + * @param type The image type of the attachment (currently only png, jpeg, and pdf are supported) + * @param input The byte data of the attachment. + * + * Note: the given input won't be closed by this method. + * + * See also [the API documentation](https://api-doc.qonto.com/docs/business-api/reference/openapi_v2.yml/paths/~1v2~1transactions~1%7Btransaction_id%7D~1attachments/post) + */ + suspend fun addAttachment(transactionInternalId: String, type: AttachmentType, input: AttachmentByteInput) + + /** + * Remove an attachment from a transaction + * + * @param transactionInternalId The internal id of the Transaction - e.g. `4c306508-dac9-410b-9937-e87b02462e42` + * @param attachmentId The id of the attachment to remove - e.g. `4c306508-dac9-410b-9937-e87b02462e42` + * + * See also [the API documentation](https://api-doc.qonto.com/docs/business-api/reference/openapi_v2.yml/paths/~1v2~1transactions~1%7Btransaction_id%7D~1attachments~1%7Bid%7D/delete) + */ + suspend fun removeAttachment(transactionInternalId: String, attachmentId: String) + + /** + * Remove all attachments from a transaction. + * + * @param transactionInternalId The internal id of the Transaction - e.g. `4c306508-dac9-410b-9937-e87b02462e42` + * + * See also [the API documentation](https://api-doc.qonto.com/docs/business-api/reference/openapi_v2.yml/paths/~1v2~1transactions~1%7Btransaction_id%7D~1attachments/delete) + */ + suspend fun removeAllAttachments(transactionInternalId: String) } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/blocking/BlockingQontoClient.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/blocking/BlockingQontoClient.kt index 304ebb6..24c9f40 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/blocking/BlockingQontoClient.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/blocking/BlockingQontoClient.kt @@ -29,6 +29,8 @@ package org.jraf.klibqonto.client.blocking import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.internal.client.blocking.BlockingQontoClientImpl import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -61,11 +63,7 @@ interface BlockingQontoClient { */ fun getLoginUri( oAuthCredentials: OAuthCredentials, - scopes: List = listOf( - OAuthScope.OFFLINE_ACCESS, - OAuthScope.ORGANIZATION_READ, - OAuthScope.OPENID, - ), + scopes: List = OAuthScope.values().toList(), uniqueState: String, ): String @@ -158,6 +156,21 @@ interface BlockingQontoClient { * See [QontoClient.Attachments.getAttachmentList]. */ fun getAttachmentList(transactionInternalId: String): List + + /** + * See [QontoClient.Attachments.addAttachment]. + */ + fun addAttachment(transactionInternalId: String, type: AttachmentType, input: AttachmentByteInput) + + /** + * See [QontoClient.Attachments.removeAttachment]. + */ + fun removeAttachment(transactionInternalId: String, attachmentId: String) + + /** + * See [QontoClient.Attachments.removeAllAttachments]. + */ + fun removeAllAttachments(transactionInternalId: String) } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/CallbackQontoClient.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/CallbackQontoClient.kt index e41506b..d0d3ac8 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/CallbackQontoClient.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/CallbackQontoClient.kt @@ -29,6 +29,8 @@ package org.jraf.klibqonto.client.callback import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.internal.client.callback.CallbackQontoClientImpl import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -62,11 +64,7 @@ interface CallbackQontoClient { */ fun getLoginUri( oAuthCredentials: OAuthCredentials, - scopes: List = listOf( - OAuthScope.OFFLINE_ACCESS, - OAuthScope.ORGANIZATION_READ, - OAuthScope.OPENID, - ), + scopes: List = OAuthScope.values().toList(), uniqueState: String, ): String @@ -172,6 +170,26 @@ interface CallbackQontoClient { transactionInternalId: String, onResult: (Result>) -> Unit, ) + + /** + * See [QontoClient.Attachments.addAttachment]. + */ + fun addAttachment( + transactionInternalId: String, + type: AttachmentType, + input: AttachmentByteInput, + onResult: (Result) -> Unit, + ) + + /** + * See [QontoClient.Attachments.removeAttachment]. + */ + fun removeAttachment(transactionInternalId: String, attachmentId: String, onResult: (Result) -> Unit) + + /** + * See [QontoClient.Attachments.removeAllAttachments]. + */ + fun removeAllAttachments(transactionInternalId: String, onResult: (Result) -> Unit) } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/Result.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/Result.kt deleted file mode 100644 index 4896b55..0000000 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/client/callback/Result.kt +++ /dev/null @@ -1,299 +0,0 @@ -/* - * This source is part of the - * _____ ___ ____ - * __ / / _ \/ _ | / __/___ _______ _ - * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ - * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / - * /___/ - * repository. - * - * Copyright (C) 2019-present Benoit 'BoD' Lubek (BoD@JRAF.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate") - -package org.jraf.klibqonto.client.callback - -/** - * **Note: this class is a copy of [Kotlin's Result](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html), - * with `inline` removed and simplified to make it compatible with Kotlin/Native targets.** - * - * A discriminated union that encapsulates successful outcome with a value of type [T] - * or a failure with an arbitrary [Throwable] exception. - */ -class Result( - internal val value: Any? -) { - /** - * Returns `true` if this instance represents successful outcome. - * In this case [isFailure] returns `false`. - */ - val isSuccess: Boolean get() = value !is Failure - - /** - * Returns `true` if this instance represents failed outcome. - * In this case [isSuccess] returns `false`. - */ - val isFailure: Boolean get() = value is Failure - - /** - * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or `null` - * if it is [failure][Result.isFailure]. - * - * This function is shorthand for `getOrElse { null }` (see [getOrElse]) or - * `fold(onSuccess = { it }, onFailure = { null })` (see [fold]). - */ - fun getOrNull(): T? = - when { - isFailure -> null - else -> value as T - } - - /** - * Returns the encapsulated exception if this instance represents [failure][isFailure] or `null` - * if it is [success][isSuccess]. - * - * This function is shorthand for `fold(onSuccess = { null }, onFailure = { it })` (see [fold]). - */ - fun exceptionOrNull(): Throwable? = - when (value) { - is Failure -> value.exception - else -> null - } - - /** - * Returns a string `Success(v)` if this instance represents [success][Result.isSuccess] - * where `v` is a string representation of the value or a string `Failure(x)` if - * it is [failure][isFailure] where `x` is a string representation of the exception. - */ - override fun toString(): String = - when (value) { - is Failure -> value.toString() - else -> "Success($value)" - } - - /** - * Companion object for [Result] class that contains its constructor functions - * [success] and [failure]. - */ - companion object { - /** - * Returns an instance that encapsulates the given [value] as successful value. - */ - fun success(value: T): Result = - Result(value) - - /** - * Returns an instance that encapsulates the given [exception] as failure. - */ - fun failure(exception: Throwable): Result = - Result( - createFailure(exception) - ) - } - - internal class Failure( - val exception: Throwable - ) { - override fun equals(other: Any?): Boolean = other is Failure && exception == other.exception - override fun hashCode(): Int = exception.hashCode() - override fun toString(): String = "Failure($exception)" - } - - /** - * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or throws the encapsulated exception - * if it is [failure][Result.isFailure]. - * - * This function is shorthand for `getOrElse { throw it }` (see [getOrElse]). - */ - fun getOrThrow(): T { - throwOnFailure() - return value as T - } - - /** - * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the - * result of [onFailure] function for encapsulated exception if it is [failure][Result.isFailure]. - * - * Note, that an exception thrown by [onFailure] function is rethrown by this function. - * - * This function is shorthand for `fold(onSuccess = { it }, onFailure = onFailure)` (see [fold]). - */ - fun getOrElse(onFailure: (exception: Throwable) -> T): T { - return when (val exception = exceptionOrNull()) { - null -> value as T - else -> onFailure(exception) - } - } - - /** - * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the - * [defaultValue] if it is [failure][Result.isFailure]. - * - * This function is shorthand for `getOrElse { defaultValue }` (see [getOrElse]). - */ - fun getOrDefault(defaultValue: T): T { - if (isFailure) return defaultValue - return value as T - } - - /** - * Returns the the result of [onSuccess] for encapsulated value if this instance represents [success][Result.isSuccess] - * or the result of [onFailure] function for encapsulated exception if it is [failure][Result.isFailure]. - * - * Note, that an exception thrown by [onSuccess] or by [onFailure] function is rethrown by this function. - */ - fun fold( - onSuccess: (value: T) -> R, - onFailure: (exception: Throwable) -> R - ): R { - return when (val exception = exceptionOrNull()) { - null -> onSuccess(value as T) - else -> onFailure(exception) - } - } - - /** - * Returns the encapsulated result of the given [transform] function applied to encapsulated value - * if this instance represents [success][Result.isSuccess] or the - * original encapsulated exception if it is [failure][Result.isFailure]. - * - * Note, that an exception thrown by [transform] function is rethrown by this function. - * See [mapCatching] for an alternative that encapsulates exceptions. - */ - fun map(transform: (value: T) -> R): Result { - return when { - isSuccess -> success(transform(value as T)) - else -> Result(value) - } - } - - /** - * Returns the encapsulated result of the given [transform] function applied to encapsulated value - * if this instance represents [success][Result.isSuccess] or the - * original encapsulated exception if it is [failure][Result.isFailure]. - * - * Any exception thrown by [transform] function is caught, encapsulated as a failure and returned by this function. - * See [map] for an alternative that rethrows exceptions. - */ - fun mapCatching(transform: (value: T) -> R): Result { - return when { - isSuccess -> runCatching { transform(value as T) } - else -> Result(value) - } - } - - /** - * Returns the encapsulated result of the given [transform] function applied to encapsulated exception - * if this instance represents [failure][Result.isFailure] or the - * original encapsulated value if it is [success][Result.isSuccess]. - * - * Note, that an exception thrown by [transform] function is rethrown by this function. - * See [recoverCatching] for an alternative that encapsulates exceptions. - */ - fun recover(transform: (exception: Throwable) -> T): Result { - return when (val exception = exceptionOrNull()) { - null -> this - else -> success(transform(exception)) - } - } - - /** - * Returns the encapsulated result of the given [transform] function applied to encapsulated exception - * if this instance represents [failure][Result.isFailure] or the - * original encapsulated value if it is [success][Result.isSuccess]. - * - * Any exception thrown by [transform] function is caught, encapsulated as a failure and returned by this function. - * See [recover] for an alternative that rethrows exceptions. - */ - fun recoverCatching(transform: (exception: Throwable) -> T): Result { - return when (val exception = exceptionOrNull()) { - null -> this - else -> runCatching { transform(exception) } - } - } - - /** - * Performs the given [action] on encapsulated exception if this instance represents [failure][Result.isFailure]. - * Returns the original `Result` unchanged. - */ - fun onFailure(action: (exception: Throwable) -> Unit): Result { - exceptionOrNull()?.let { action(it) } - return this - } - - /** - * Performs the given [action] on encapsulated value if this instance represents [success][Result.isSuccess]. - * Returns the original `Result` unchanged. - */ - fun onSuccess(action: (value: T) -> Unit): Result { - if (isSuccess) action(value as T) - return this - } - -} - -/** - * Creates an instance of internal marker [Result.Failure] class to - * make sure that this class is not exposed in ABI. - */ -internal fun createFailure(exception: Throwable): Any = - Result.Failure(exception) - -/** - * Throws exception if the result is failure. This internal function minimizes - * inlined bytecode for [getOrThrow] and makes sure that in the future we can - * add some exception-augmenting logic here (if needed). - */ -internal fun Result<*>.throwOnFailure() { - if (value is Result.Failure) throw value.exception -} - -/** - * Calls the specified function [block] and returns its encapsulated result if invocation was successful, - * catching and encapsulating any thrown exception as a failure. - */ -fun runCatching(block: () -> R): Result { - return try { - Result.success(block()) - } catch (e: Throwable) { - Result.failure(e) - } -} - -/** - * Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result - * if invocation was successful, catching and encapsulating any thrown exception as a failure. - */ -fun T.runCatching(block: T.() -> R): Result { - return try { - Result.success(block()) - } catch (e: Throwable) { - Result.failure(e) - } -} - -/** - * Calls the specified function [block] and returns its encapsulated result if invocation was successful, - * catching and encapsulating any thrown exception as a failure. - */ -suspend fun suspendRunCatching(block: suspend () -> R): Result { - return try { - Result.success(block()) - } catch (e: Throwable) { - Result.failure(e) - } -} - diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/api/model/oauth/ApiOAuthScopeConverter.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/api/model/oauth/ApiOAuthScopeConverter.kt index aad72e8..27ad4fc 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/api/model/oauth/ApiOAuthScopeConverter.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/api/model/oauth/ApiOAuthScopeConverter.kt @@ -34,6 +34,7 @@ internal object ApiOAuthScopeConverter : ApiConverter() { "offline_access" -> OAuthScope.OFFLINE_ACCESS "organization.read" -> OAuthScope.ORGANIZATION_READ "openid" -> OAuthScope.OPENID + "attachment.write" -> OAuthScope.ATTACHMENT_WRITE else -> throw ApiConverterException("Unknown OAuth scope '$apiModel'") } @@ -43,5 +44,6 @@ internal object ApiOAuthScopeConverter : ApiConverter() { OAuthScope.OFFLINE_ACCESS -> "offline_access" OAuthScope.ORGANIZATION_READ -> "organization.read" OAuthScope.OPENID -> "openid" + OAuthScope.ATTACHMENT_WRITE -> "attachment.write" } } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoClientImpl.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoClientImpl.kt index 4ebc159..4f95335 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoClientImpl.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoClientImpl.kt @@ -66,6 +66,8 @@ import org.jraf.klibqonto.internal.api.model.transactions.ApiTransactionEnvelope import org.jraf.klibqonto.internal.api.model.transactions.ApiTransactionListEnvelopeConverter import org.jraf.klibqonto.internal.api.model.transactions.ApiTransactionStatusConverter import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -258,6 +260,22 @@ internal class QontoClientImpl( .apiToModel(ApiAttachmentListEnvelopeConverter) } + override suspend fun addAttachment( + transactionInternalId: String, + type: AttachmentType, + input: AttachmentByteInput, + ) { + service.addAttachment(transactionInternalId, type, input) + } + + override suspend fun removeAttachment(transactionInternalId: String, attachmentId: String) { + service.removeAttachment(transactionInternalId, attachmentId) + } + + override suspend fun removeAllAttachments(transactionInternalId: String) { + service.removeAllAttachments(transactionInternalId) + } + override fun close() = httpClient.close() } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoService.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoService.kt index 2b537fb..d37ad98 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoService.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/QontoService.kt @@ -25,15 +25,21 @@ package org.jraf.klibqonto.internal.client import io.ktor.client.HttpClient +import io.ktor.client.request.delete import io.ktor.client.request.forms.FormDataContent +import io.ktor.client.request.forms.append +import io.ktor.client.request.forms.formData +import io.ktor.client.request.forms.submitFormWithBinaryData import io.ktor.client.request.get import io.ktor.client.request.header import io.ktor.client.request.parameter import io.ktor.client.request.post +import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.http.Parameters import io.ktor.util.InternalAPI import io.ktor.util.encodeBase64 +import io.ktor.utils.io.core.writeFully import org.jraf.klibqonto.client.BaseUri import org.jraf.klibqonto.client.ClientConfiguration import org.jraf.klibqonto.internal.api.model.attachments.ApiAttachmentEnvelope @@ -44,6 +50,8 @@ import org.jraf.klibqonto.internal.api.model.oauth.ApiOAuthTokens import org.jraf.klibqonto.internal.api.model.organizations.ApiOrganizationEnvelope import org.jraf.klibqonto.internal.api.model.transactions.ApiTransactionEnvelope import org.jraf.klibqonto.internal.api.model.transactions.ApiTransactionListEnvelope +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType internal class QontoService( clientConfiguration: ClientConfiguration, @@ -181,4 +189,40 @@ internal class QontoService( return httpClient.get(apiBaseUri + "transactions/$transactionInternalId/attachments") } + suspend fun addAttachment(transactionInternalId: String, type: AttachmentType, input: AttachmentByteInput) { + httpClient.submitFormWithBinaryData( + url = apiBaseUri + "transactions/$transactionInternalId/attachments", + formData = formData { + append( + key = "file", + filename = "file." + when (type) { + AttachmentType.PNG -> "png" + AttachmentType.JPEG -> "jpg" + AttachmentType.PDF -> "pdf" + }, + contentType = when (type) { + AttachmentType.PNG -> ContentType.Image.PNG + AttachmentType.JPEG -> ContentType.Image.JPEG + AttachmentType.PDF -> ContentType.Application.Pdf + } + ) { + val buffer = ByteArray(1024) + var read: Int + do { + read = input.read(buffer) + if (read > 0) writeFully(buffer, 0, read) + } while (read == buffer.size) + input.close() + } + } + ) + } + + suspend fun removeAttachment(transactionInternalId: String, attachmentId: String) { + httpClient.delete(apiBaseUri + "transactions/$transactionInternalId/attachments/$attachmentId") + } + + suspend fun removeAllAttachments(transactionInternalId: String) { + httpClient.delete(apiBaseUri + "transactions/$transactionInternalId/attachments") + } } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/blocking/BlockingQontoClientImpl.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/blocking/BlockingQontoClientImpl.kt index ec8e8e0..10aaad8 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/blocking/BlockingQontoClientImpl.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/blocking/BlockingQontoClientImpl.kt @@ -28,6 +28,8 @@ import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.client.blocking.BlockingQontoClient import org.jraf.klibqonto.internal.client.runBlocking import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -131,5 +133,18 @@ internal class BlockingQontoClientImpl( qontoClient.attachments.getAttachmentList(transactionInternalId) } + override fun addAttachment(transactionInternalId: String, type: AttachmentType, input: AttachmentByteInput) = + runBlocking { + qontoClient.attachments.addAttachment(transactionInternalId, type, input) + } + + override fun removeAttachment(transactionInternalId: String, attachmentId: String) = runBlocking { + qontoClient.attachments.removeAttachment(transactionInternalId, attachmentId) + } + + override fun removeAllAttachments(transactionInternalId: String) = runBlocking { + qontoClient.attachments.removeAllAttachments(transactionInternalId) + } + override fun close() = qontoClient.close() } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/callback/CallbackQontoClientImpl.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/callback/CallbackQontoClientImpl.kt index 36f5fc4..adb6ab6 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/callback/CallbackQontoClientImpl.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/internal/client/callback/CallbackQontoClientImpl.kt @@ -27,10 +27,10 @@ package org.jraf.klibqonto.internal.client.callback import kotlinx.coroutines.launch import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.client.callback.CallbackQontoClient -import org.jraf.klibqonto.client.callback.Result -import org.jraf.klibqonto.client.callback.suspendRunCatching import org.jraf.klibqonto.internal.client.klibQontoScope import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -155,6 +155,28 @@ internal class CallbackQontoClientImpl( qontoClient.attachments.getAttachmentList(transactionInternalId) } + override fun addAttachment( + transactionInternalId: String, + type: AttachmentType, + input: AttachmentByteInput, + onResult: (Result) -> Unit, + ) = launchAndCallback(onResult) { + qontoClient.attachments.addAttachment(transactionInternalId, type, input) + } + + override fun removeAttachment( + transactionInternalId: String, + attachmentId: String, + onResult: (Result) -> Unit, + ) = launchAndCallback(onResult) { + qontoClient.attachments.removeAttachment(transactionInternalId, attachmentId) + } + + override fun removeAllAttachments(transactionInternalId: String, onResult: (Result) -> Unit) = + launchAndCallback(onResult) { + qontoClient.attachments.removeAllAttachments(transactionInternalId) + } + override fun close() = qontoClient.close() private fun launchAndCallback( @@ -162,7 +184,7 @@ internal class CallbackQontoClientImpl( block: suspend () -> T, ) { klibQontoScope.launch { - onResult(suspendRunCatching(block)) + onResult(runCatching { block() }) } } } diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/AttachmentByteInput.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/AttachmentByteInput.kt new file mode 100644 index 0000000..502933c --- /dev/null +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/AttachmentByteInput.kt @@ -0,0 +1,40 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2019-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments + +interface AttachmentByteInput { + /** + * Reads bytes from the input stream into an array of bytes. + * An attempt is made to read as many as [byteArray].size bytes, but a smaller number may be read. + * + * The number of bytes actually read is returned as an integer. + */ + fun read(byteArray: ByteArray): Int + + /** + * Called when the whole attachment has been fully read. + */ + fun close() +} diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/AttachmentType.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/AttachmentType.kt new file mode 100644 index 0000000..7f745bf --- /dev/null +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/AttachmentType.kt @@ -0,0 +1,31 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2021-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments + +enum class AttachmentType { + PNG, + JPEG, + PDF, +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt new file mode 100644 index 0000000..3d8443e --- /dev/null +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt @@ -0,0 +1,29 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2021-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments.file + +import org.jraf.klibqonto.model.attachments.AttachmentByteInput + +expect class FileAttachmentByteInput(filePath: String) : AttachmentByteInput \ No newline at end of file diff --git a/library/src/commonMain/kotlin/org/jraf/klibqonto/model/oauth/OAuthScope.kt b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/oauth/OAuthScope.kt index df2dfbc..fdf937d 100644 --- a/library/src/commonMain/kotlin/org/jraf/klibqonto/model/oauth/OAuthScope.kt +++ b/library/src/commonMain/kotlin/org/jraf/klibqonto/model/oauth/OAuthScope.kt @@ -28,4 +28,5 @@ enum class OAuthScope { OFFLINE_ACCESS, ORGANIZATION_READ, OPENID, + ATTACHMENT_WRITE, } \ No newline at end of file diff --git a/library/src/iosMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt b/library/src/iosMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt new file mode 100644 index 0000000..fd8c738 --- /dev/null +++ b/library/src/iosMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt @@ -0,0 +1,61 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2021-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments.file + +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.usePinned +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import platform.Foundation.NSData +import platform.Foundation.NSFileHandle +import platform.Foundation.closeFile +import platform.Foundation.fileHandleForReadingAtPath +import platform.Foundation.readDataOfLength +import platform.posix.memcpy + +actual class FileAttachmentByteInput actual constructor(filePath: String) : AttachmentByteInput { + private val nsFileHandle: NSFileHandle by lazy { NSFileHandle.fileHandleForReadingAtPath(filePath)!! } + + override fun read(byteArray: ByteArray): Int { + val nsData: NSData = nsFileHandle.readDataOfLength(byteArray.size.toULong()) + nsData.toByteArray().copyInto(byteArray) + return nsData.length.toInt() + } + + override fun close() { + nsFileHandle.closeFile() + } +} + +// Taken from https://medium.com/kodein-koders/create-a-kotlin-multiplatform-library-with-swift-1a818b2dc1b0 +private fun NSData.toByteArray(): ByteArray { + val size = length.toInt() + val byteArray = ByteArray(size) + if (size > 0) { + byteArray.usePinned { pinned -> + memcpy(pinned.addressOf(0), this.bytes, this.length) + } + } + return byteArray +} \ No newline at end of file diff --git a/library/src/jvmMain/kotlin/org/jraf/klibqonto/client/future/FutureQontoClient.kt b/library/src/jvmMain/kotlin/org/jraf/klibqonto/client/future/FutureQontoClient.kt index 45ca250..88f2e5c 100644 --- a/library/src/jvmMain/kotlin/org/jraf/klibqonto/client/future/FutureQontoClient.kt +++ b/library/src/jvmMain/kotlin/org/jraf/klibqonto/client/future/FutureQontoClient.kt @@ -29,6 +29,8 @@ package org.jraf.klibqonto.client.future import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.internal.client.future.FutureQontoClientImpl import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -60,11 +62,7 @@ interface FutureQontoClient { */ fun getLoginUri( oAuthCredentials: OAuthCredentials, - scopes: List = listOf( - OAuthScope.OFFLINE_ACCESS, - OAuthScope.ORGANIZATION_READ, - OAuthScope.OPENID, - ), + scopes: List = OAuthScope.values().toList(), uniqueState: String, ): String @@ -156,6 +154,25 @@ interface FutureQontoClient { * See [QontoClient.Attachments.getAttachmentList]. */ fun getAttachmentList(transactionInternalId: String): Future> + + /** + * See [QontoClient.Attachments.addAttachment]. + */ + fun addAttachment( + transactionInternalId: String, + type: AttachmentType, + input: AttachmentByteInput, + ): Future + + /** + * See [QontoClient.Attachments.removeAttachment]. + */ + fun removeAttachment(transactionInternalId: String, attachmentId: String): Future + + /** + * See [QontoClient.Attachments.removeAllAttachments]. + */ + fun removeAllAttachments(transactionInternalId: String): Future } diff --git a/library/src/jvmMain/kotlin/org/jraf/klibqonto/internal/client/future/FutureQontoClientImpl.kt b/library/src/jvmMain/kotlin/org/jraf/klibqonto/internal/client/future/FutureQontoClientImpl.kt index 478152d..f302fcc 100644 --- a/library/src/jvmMain/kotlin/org/jraf/klibqonto/internal/client/future/FutureQontoClientImpl.kt +++ b/library/src/jvmMain/kotlin/org/jraf/klibqonto/internal/client/future/FutureQontoClientImpl.kt @@ -29,6 +29,8 @@ import kotlinx.coroutines.future.future import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.client.future.FutureQontoClient import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import org.jraf.klibqonto.model.attachments.AttachmentType import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.labels.Label import org.jraf.klibqonto.model.memberships.Membership @@ -130,5 +132,24 @@ internal class FutureQontoClientImpl( qontoClient.attachments.getAttachmentList(transactionInternalId) } + override fun addAttachment( + transactionInternalId: String, + type: AttachmentType, + input: AttachmentByteInput, + ) = GlobalScope.future { + qontoClient.attachments.addAttachment(transactionInternalId, type, input) + null + } + + override fun removeAttachment(transactionInternalId: String, attachmentId: String) = GlobalScope.future { + qontoClient.attachments.removeAttachment(transactionInternalId, attachmentId) + null + } + + override fun removeAllAttachments(transactionInternalId: String) = GlobalScope.future { + qontoClient.attachments.removeAllAttachments(transactionInternalId) + null + } + override fun close() = qontoClient.close() } diff --git a/library/src/jvmMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt b/library/src/jvmMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt new file mode 100644 index 0000000..e99a840 --- /dev/null +++ b/library/src/jvmMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt @@ -0,0 +1,40 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2021-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments.file + +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import java.io.File + +actual class FileAttachmentByteInput actual constructor(filePath: String) : AttachmentByteInput { + private val inputStream by lazy { File(filePath).inputStream() } + + override fun read(byteArray: ByteArray): Int { + return inputStream.read(byteArray, 0, byteArray.size) + } + + override fun close() { + inputStream.close() + } +} \ No newline at end of file diff --git a/library/src/macosMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt b/library/src/macosMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt new file mode 100644 index 0000000..fd8c738 --- /dev/null +++ b/library/src/macosMain/kotlin/org/jraf/klibqonto/model/attachments/file/FileAttachmentByteInput.kt @@ -0,0 +1,61 @@ +/* + * This source is part of the + * _____ ___ ____ + * __ / / _ \/ _ | / __/___ _______ _ + * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ + * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / + * /___/ + * repository. + * + * Copyright (C) 2021-present Benoit 'BoD' Lubek (BoD@JRAF.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jraf.klibqonto.model.attachments.file + +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.usePinned +import org.jraf.klibqonto.model.attachments.AttachmentByteInput +import platform.Foundation.NSData +import platform.Foundation.NSFileHandle +import platform.Foundation.closeFile +import platform.Foundation.fileHandleForReadingAtPath +import platform.Foundation.readDataOfLength +import platform.posix.memcpy + +actual class FileAttachmentByteInput actual constructor(filePath: String) : AttachmentByteInput { + private val nsFileHandle: NSFileHandle by lazy { NSFileHandle.fileHandleForReadingAtPath(filePath)!! } + + override fun read(byteArray: ByteArray): Int { + val nsData: NSData = nsFileHandle.readDataOfLength(byteArray.size.toULong()) + nsData.toByteArray().copyInto(byteArray) + return nsData.length.toInt() + } + + override fun close() { + nsFileHandle.closeFile() + } +} + +// Taken from https://medium.com/kodein-koders/create-a-kotlin-multiplatform-library-with-swift-1a818b2dc1b0 +private fun NSData.toByteArray(): ByteArray { + val size = length.toInt() + val byteArray = ByteArray(size) + if (size > 0) { + byteArray.usePinned { pinned -> + memcpy(pinned.addressOf(0), this.bytes, this.length) + } + } + return byteArray +} \ No newline at end of file diff --git a/samples/sample-jvm/src/main/java/org/jraf/klibqonto/sample/FutureSample.java b/samples/sample-jvm/src/main/java/org/jraf/klibqonto/sample/FutureSample.java index c2afb74..f8ab803 100644 --- a/samples/sample-jvm/src/main/java/org/jraf/klibqonto/sample/FutureSample.java +++ b/samples/sample-jvm/src/main/java/org/jraf/klibqonto/sample/FutureSample.java @@ -28,6 +28,8 @@ import org.jraf.klibqonto.client.future.FutureQontoClient; import org.jraf.klibqonto.client.future.FutureQontoClientUtils; import org.jraf.klibqonto.model.attachments.Attachment; +import org.jraf.klibqonto.model.attachments.AttachmentType; +import org.jraf.klibqonto.model.attachments.file.FileAttachmentByteInput; import org.jraf.klibqonto.model.dates.DateRange; import org.jraf.klibqonto.model.labels.Label; import org.jraf.klibqonto.model.memberships.Membership; @@ -49,6 +51,12 @@ class FutureSample { private static final String LOGIN = "xxx"; private static final String SECRET_KEY = "yyy"; + // Replace this with a transaction internal id that exists + private static final String TRANSACTION_INTERNAL_ID = "00000000-0000-0000-0000-000000000000"; + + // Replace this to a path to a pdf file that exists + private static final String PATH_TO_A_PDF_FILE = "/tmp/file.pdf"; + private FutureQontoClient client; private void initClient() { @@ -100,6 +108,27 @@ private void main() throws Exception { Attachment attachment = getAttachment(transactionList); System.out.println(attachment); + // Get all the attachments of a specific transaction + System.out.println("\n\nAttachments of transaction:"); + List attachmentList = client.getAttachments().getAttachmentList(TRANSACTION_INTERNAL_ID).get(); + System.out.println(attachmentList); + + // Remove the last attachment + String attachmentId = attachmentList.get(attachmentList.size() - 1).getId(); + client.getAttachments().removeAttachment(TRANSACTION_INTERNAL_ID, attachmentId); + System.out.println("Attachment " + attachmentId + " removed"); + + // Add an attachment + client.getAttachments().addAttachment( + TRANSACTION_INTERNAL_ID, + AttachmentType.PDF, + new FileAttachmentByteInput(PATH_TO_A_PDF_FILE) + ).get(); + System.out.println("Attachment added"); + + // Remove all attachments +// client.getAttachments().removeAllAttachments(TRANSACTION_INTERNAL_ID).get(); +// System.out.println("All attachments removed"); } catch (Throwable t) { t.printStackTrace(); } finally { diff --git a/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/CallbackSample.kt b/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/CallbackSample.kt index b811a06..f7684de 100644 --- a/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/CallbackSample.kt +++ b/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/CallbackSample.kt @@ -24,7 +24,6 @@ package org.jraf.klibqonto.sample -import org.jraf.klibqonto.client.Authentication import org.jraf.klibqonto.client.ClientConfiguration import org.jraf.klibqonto.client.HttpConfiguration import org.jraf.klibqonto.client.HttpLoggingLevel @@ -32,7 +31,6 @@ import org.jraf.klibqonto.client.HttpProxy import org.jraf.klibqonto.client.LoginSecretKeyAuthentication import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.client.callback.CallbackQontoClient -import org.jraf.klibqonto.client.callback.Result import org.jraf.klibqonto.client.callback.asCallbackQontoClient import org.jraf.klibqonto.model.attachments.Attachment import org.jraf.klibqonto.model.dates.DateRange @@ -165,7 +163,7 @@ class CallbackSample { private fun getTransactionList( organization: Organization, - onResult: (Result>) -> Unit + onResult: (Result>) -> Unit, ) { val res = mutableListOf() @@ -255,7 +253,7 @@ class CallbackSample { callbackQontoClientMemberships: CallbackQontoClient.Memberships, pagination: Pagination, allMembershipList: MutableList, - onResult: (Result>) -> Unit + onResult: (Result>) -> Unit, ) { callbackQontoClientMemberships.getMembershipList(pagination) { result -> result.fold( diff --git a/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/Sample.kt b/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/Sample.kt index addb071..4d720ef 100644 --- a/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/Sample.kt +++ b/samples/sample-jvm/src/main/kotlin/org/jraf/klibqonto/sample/Sample.kt @@ -33,6 +33,8 @@ import org.jraf.klibqonto.client.LoginSecretKeyAuthentication import org.jraf.klibqonto.client.OAuthAuthentication import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentType +import org.jraf.klibqonto.model.attachments.file.FileAttachmentByteInput import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.memberships.Membership import org.jraf.klibqonto.model.oauth.OAuthCredentials @@ -60,6 +62,9 @@ private const val USE_OAUTH = false // Replace this with a transaction internal id that exists private const val TRANSACTION_INTERNAL_ID = "00000000-0000-0000-0000-000000000000" +// Replace this to a path to a pdf file that exists +private const val PATH_TO_A_PDF_FILE = "/tmp/file.pdf" + class Sample { private val oAuthCredentials = OAuthCredentials( clientId = OAUTH_CLIENT_ID, @@ -121,6 +126,7 @@ class Sample { oAuthCredentials = oAuthCredentials, code = codeAndUniqueState.code ) + // You can save these, and re-use them later. println(tokens) // 4/ Use obtained tokens for subsequent API calls @@ -174,6 +180,26 @@ class Sample { println("\n\nAttachments of transaction:") val attachmentList = client.attachments.getAttachmentList(TRANSACTION_INTERNAL_ID) println(attachmentList) + + // Remove the last attachment + val attachmentId = attachmentList.last().id + client.attachments.removeAttachment( + transactionInternalId = TRANSACTION_INTERNAL_ID, + attachmentId = attachmentId + ) + println("Attachment $attachmentId removed") + + // Add an attachment + client.attachments.addAttachment( + transactionInternalId = TRANSACTION_INTERNAL_ID, + type = AttachmentType.PDF, + input = FileAttachmentByteInput(PATH_TO_A_PDF_FILE) + ) + println("Attachment added") + + // Remove all attachments + // client.attachments.removeAllAttachments(TRANSACTION_INTERNAL_ID) + // println("All attachments removed") } // Close @@ -192,8 +218,8 @@ class Sample { bankAccountSlug = bankAccountSlug, status = setOf(Transaction.Status.COMPLETED, Transaction.Status.DECLINED), updatedDateRange = DateRange( - date("2018-01-01"), - date("2019-12-31") + date("2020-01-01"), + date("2021-12-31") ), sortField = QontoClient.Transactions.SortField.UPDATED_DATE, pagination = Pagination(itemsPerPage = 10) @@ -206,8 +232,8 @@ class Sample { bankAccountSlug = bankAccountSlug, status = setOf(Transaction.Status.COMPLETED, Transaction.Status.DECLINED), updatedDateRange = DateRange( - date("2018-01-01"), - date("2019-12-31") + date("2020-01-01"), + date("2021-12-31") ), sortField = QontoClient.Transactions.SortField.UPDATED_DATE, pagination = nextPagination diff --git a/samples/sample-native-osx/src/macosMain/kotlin/Sample.kt b/samples/sample-native-osx/src/macosMain/kotlin/Sample.kt index 23251c1..504d5c7 100644 --- a/samples/sample-native-osx/src/macosMain/kotlin/Sample.kt +++ b/samples/sample-native-osx/src/macosMain/kotlin/Sample.kt @@ -31,6 +31,8 @@ import org.jraf.klibqonto.client.LoginSecretKeyAuthentication import org.jraf.klibqonto.client.OAuthAuthentication import org.jraf.klibqonto.client.QontoClient import org.jraf.klibqonto.model.attachments.Attachment +import org.jraf.klibqonto.model.attachments.AttachmentType +import org.jraf.klibqonto.model.attachments.file.FileAttachmentByteInput import org.jraf.klibqonto.model.dates.Date import org.jraf.klibqonto.model.dates.DateRange import org.jraf.klibqonto.model.memberships.Membership @@ -60,6 +62,9 @@ private const val USE_OAUTH = false // Replace this with a transaction internal id that exists private const val TRANSACTION_INTERNAL_ID = "00000000-0000-0000-0000-000000000000" +// Replace this to a path to a pdf file that exists +private const val PATH_TO_A_PDF_FILE = "/tmp/file.pdf" + class Sample { private val oAuthCredentials = OAuthCredentials( clientId = OAUTH_CLIENT_ID, @@ -121,6 +126,7 @@ class Sample { oAuthCredentials = oAuthCredentials, code = codeAndUniqueState.code ) + // You can save these, and re-use them later. println(tokens) // 4/ Use obtained tokens for subsequent API calls @@ -174,6 +180,26 @@ class Sample { println("\n\nAttachments of transaction:") val attachmentList = client.attachments.getAttachmentList(TRANSACTION_INTERNAL_ID) println(attachmentList) + + // Remove the last attachment + val attachmentId = attachmentList.last().id + client.attachments.removeAttachment( + transactionInternalId = TRANSACTION_INTERNAL_ID, + attachmentId = attachmentId + ) + println("Attachment $attachmentId removed") + + // Add an attachment + client.attachments.addAttachment( + transactionInternalId = TRANSACTION_INTERNAL_ID, + type = AttachmentType.PDF, + input = FileAttachmentByteInput(PATH_TO_A_PDF_FILE) + ) + println("Attachment added") + + // Remove all attachments + // client.attachments.removeAllAttachments(TRANSACTION_INTERNAL_ID) + // println("All attachments removed") } // Close @@ -192,8 +218,8 @@ class Sample { bankAccountSlug = bankAccountSlug, status = setOf(Transaction.Status.COMPLETED, Transaction.Status.DECLINED), updatedDateRange = DateRange( - date("2018-01-01"), - date("2019-12-31") + date("2020-01-01"), + date("2021-12-31") ), sortField = QontoClient.Transactions.SortField.UPDATED_DATE, pagination = Pagination(itemsPerPage = 10) @@ -206,8 +232,8 @@ class Sample { bankAccountSlug = bankAccountSlug, status = setOf(Transaction.Status.COMPLETED, Transaction.Status.DECLINED), updatedDateRange = DateRange( - date("2018-01-01"), - date("2019-12-31") + date("2020-01-01"), + date("2021-12-31") ), sortField = QontoClient.Transactions.SortField.UPDATED_DATE, pagination = nextPagination