diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 59bb50ff..b9e06599 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.20.2" + ".": "4.21.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 9e3b99b6..809d9f7f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 138 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-11e1931d767cef402bbed5a965746d1933ae72db0f41881860593a0b1424fd80.yml -openapi_spec_hash: 9def941805f233f8761c4bdae88f6137 -config_hash: 6c3a754258f0f77e9032a90a478ab76d +configured_endpoints: 139 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-def1e982add0411bf64b48cdf5e4f2c1b85b9bd0ada0d5e6fc4869afa85e0bff.yml +openapi_spec_hash: 5d129cdf800798bcd4a93585ec89c696 +config_hash: dcd58abf1e56b114bdce22336ca3d570 diff --git a/CHANGELOG.md b/CHANGELOG.md index 58efece7..d12950c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 4.21.0 (2026-07-14) + +Full Changelog: [v4.20.2...v4.21.0](https://github.com/trycourier/courier-java/compare/v4.20.2...v4.21.0) + +### Features + +* Document POST /notifications/{id}/duplicate [C-18607] ([#165](https://github.com/trycourier/courier-java/issues/165)) ([2d6de78](https://github.com/trycourier/courier-java/commit/2d6de78b8dca5f167d42c3e2d136998278ddc80d)) +* Merge pull request [#149](https://github.com/trycourier/courier-java/issues/149) from trycourier/geraldosilva/c-19201-notification-template-subscription-topic-id ([74a301f](https://github.com/trycourier/courier-java/commit/74a301ff09a28e668a231af1ba87417bd637b8dd)) + ## 4.20.2 (2026-07-10) Full Changelog: [v4.20.1...v4.20.2](https://github.com/trycourier/courier-java/compare/v4.20.1...v4.20.2) diff --git a/build.gradle.kts b/build.gradle.kts index 13572d6b..58362a3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.courier" - version = "4.20.2" // x-release-please-version + version = "4.21.0" // x-release-please-version } subprojects { diff --git a/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationDuplicateParams.kt b/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationDuplicateParams.kt new file mode 100644 index 00000000..5352b9d3 --- /dev/null +++ b/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationDuplicateParams.kt @@ -0,0 +1,238 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.notifications + +import com.courier.core.JsonValue +import com.courier.core.Params +import com.courier.core.http.Headers +import com.courier.core.http.QueryParams +import com.courier.core.toImmutable +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Duplicate a notification template. Creates a standalone copy within the same workspace and + * environment, with " COPY" appended to the title. The copy clones the source draft's tags, brand, + * subscription topic, routing strategy, channels, and content, and is always created as a + * standalone template (it is not linked to any journey or broadcast, even if the source was). + * Templates that are scoped to a journey or a broadcast cannot be duplicated through this endpoint. + */ +class NotificationDuplicateParams +private constructor( + private val id: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, + private val additionalBodyProperties: Map, +) : Params { + + fun id(): Optional = Optional.ofNullable(id) + + /** Additional body properties to send with the request. */ + fun _additionalBodyProperties(): Map = additionalBodyProperties + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): NotificationDuplicateParams = builder().build() + + /** + * Returns a mutable builder for constructing an instance of [NotificationDuplicateParams]. + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [NotificationDuplicateParams]. */ + class Builder internal constructor() { + + private var id: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + private var additionalBodyProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(notificationDuplicateParams: NotificationDuplicateParams) = apply { + id = notificationDuplicateParams.id + additionalHeaders = notificationDuplicateParams.additionalHeaders.toBuilder() + additionalQueryParams = notificationDuplicateParams.additionalQueryParams.toBuilder() + additionalBodyProperties = + notificationDuplicateParams.additionalBodyProperties.toMutableMap() + } + + fun id(id: String?) = apply { this.id = id } + + /** Alias for calling [Builder.id] with `id.orElse(null)`. */ + fun id(id: Optional) = id(id.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + this.additionalBodyProperties.clear() + putAllAdditionalBodyProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + additionalBodyProperties.put(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { + additionalBodyProperties.remove(key) + } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalBodyProperty) + } + + /** + * Returns an immutable instance of [NotificationDuplicateParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): NotificationDuplicateParams = + NotificationDuplicateParams( + id, + additionalHeaders.build(), + additionalQueryParams.build(), + additionalBodyProperties.toImmutable(), + ) + } + + fun _body(): Optional> = + Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + + fun _pathParam(index: Int): String = + when (index) { + 0 -> id ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is NotificationDuplicateParams && + id == other.id && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams && + additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int = + Objects.hash(id, additionalHeaders, additionalQueryParams, additionalBodyProperties) + + override fun toString() = + "NotificationDuplicateParams{id=$id, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" +} diff --git a/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationTemplateSummary.kt b/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationTemplateSummary.kt index 9df42446..fecacd3d 100644 --- a/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationTemplateSummary.kt +++ b/courier-java-core/src/main/kotlin/com/courier/models/notifications/NotificationTemplateSummary.kt @@ -30,6 +30,7 @@ private constructor( private val name: JsonField, private val state: JsonField, private val tags: JsonField>, + private val subscriptionTopicId: JsonField, private val updated: JsonField, private val updater: JsonField, private val additionalProperties: MutableMap, @@ -43,9 +44,23 @@ private constructor( @JsonProperty("name") @ExcludeMissing name: JsonField = JsonMissing.of(), @JsonProperty("state") @ExcludeMissing state: JsonField = JsonMissing.of(), @JsonProperty("tags") @ExcludeMissing tags: JsonField> = JsonMissing.of(), + @JsonProperty("subscription_topic_id") + @ExcludeMissing + subscriptionTopicId: JsonField = JsonMissing.of(), @JsonProperty("updated") @ExcludeMissing updated: JsonField = JsonMissing.of(), @JsonProperty("updater") @ExcludeMissing updater: JsonField = JsonMissing.of(), - ) : this(id, created, creator, name, state, tags, updated, updater, mutableMapOf()) + ) : this( + id, + created, + creator, + name, + state, + tags, + subscriptionTopicId, + updated, + updater, + mutableMapOf(), + ) /** * @throws CourierInvalidDataException if the JSON field has an unexpected type or is @@ -87,6 +102,16 @@ private constructor( */ fun tags(): List = tags.getRequired("tags") + /** + * The linked subscription (preference) topic of the published version. Omitted when no topic is + * linked or the template has never been published. + * + * @throws CourierInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun subscriptionTopicId(): Optional = + subscriptionTopicId.getOptional("subscription_topic_id") + /** * Epoch milliseconds of last update. * @@ -145,6 +170,16 @@ private constructor( */ @JsonProperty("tags") @ExcludeMissing fun _tags(): JsonField> = tags + /** + * Returns the raw JSON value of [subscriptionTopicId]. + * + * Unlike [subscriptionTopicId], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("subscription_topic_id") + @ExcludeMissing + fun _subscriptionTopicId(): JsonField = subscriptionTopicId + /** * Returns the raw JSON value of [updated]. * @@ -198,6 +233,7 @@ private constructor( private var name: JsonField? = null private var state: JsonField? = null private var tags: JsonField>? = null + private var subscriptionTopicId: JsonField = JsonMissing.of() private var updated: JsonField = JsonMissing.of() private var updater: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @@ -210,6 +246,7 @@ private constructor( name = notificationTemplateSummary.name state = notificationTemplateSummary.state tags = notificationTemplateSummary.tags.map { it.toMutableList() } + subscriptionTopicId = notificationTemplateSummary.subscriptionTopicId updated = notificationTemplateSummary.updated updater = notificationTemplateSummary.updater additionalProperties = notificationTemplateSummary.additionalProperties.toMutableMap() @@ -289,6 +326,24 @@ private constructor( tags = (tags ?: JsonField.of(mutableListOf())).also { checkKnown("tags", it).add(tag) } } + /** + * The linked subscription (preference) topic of the published version. Omitted when no + * topic is linked or the template has never been published. + */ + fun subscriptionTopicId(subscriptionTopicId: String) = + subscriptionTopicId(JsonField.of(subscriptionTopicId)) + + /** + * Sets [Builder.subscriptionTopicId] to an arbitrary JSON value. + * + * You should usually call [Builder.subscriptionTopicId] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun subscriptionTopicId(subscriptionTopicId: JsonField) = apply { + this.subscriptionTopicId = subscriptionTopicId + } + /** Epoch milliseconds of last update. */ fun updated(updated: Long) = updated(JsonField.of(updated)) @@ -355,6 +410,7 @@ private constructor( checkRequired("name", name), checkRequired("state", state), checkRequired("tags", tags).map { it.toImmutable() }, + subscriptionTopicId, updated, updater, additionalProperties.toMutableMap(), @@ -382,6 +438,7 @@ private constructor( name() state().validate() tags() + subscriptionTopicId() updated() updater() validated = true @@ -408,6 +465,7 @@ private constructor( (if (name.asKnown().isPresent) 1 else 0) + (state.asKnown().getOrNull()?.validity() ?: 0) + (tags.asKnown().getOrNull()?.size ?: 0) + + (if (subscriptionTopicId.asKnown().isPresent) 1 else 0) + (if (updated.asKnown().isPresent) 1 else 0) + (if (updater.asKnown().isPresent) 1 else 0) @@ -557,6 +615,7 @@ private constructor( name == other.name && state == other.state && tags == other.tags && + subscriptionTopicId == other.subscriptionTopicId && updated == other.updated && updater == other.updater && additionalProperties == other.additionalProperties @@ -570,6 +629,7 @@ private constructor( name, state, tags, + subscriptionTopicId, updated, updater, additionalProperties, @@ -579,5 +639,5 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "NotificationTemplateSummary{id=$id, created=$created, creator=$creator, name=$name, state=$state, tags=$tags, updated=$updated, updater=$updater, additionalProperties=$additionalProperties}" + "NotificationTemplateSummary{id=$id, created=$created, creator=$creator, name=$name, state=$state, tags=$tags, subscriptionTopicId=$subscriptionTopicId, updated=$updated, updater=$updater, additionalProperties=$additionalProperties}" } diff --git a/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsync.kt b/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsync.kt index 37cfa66a..795006ee 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsync.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsync.kt @@ -9,6 +9,7 @@ import com.courier.core.http.HttpResponseFor import com.courier.models.notifications.NotificationArchiveParams import com.courier.models.notifications.NotificationContentMutationResponse import com.courier.models.notifications.NotificationCreateParams +import com.courier.models.notifications.NotificationDuplicateParams import com.courier.models.notifications.NotificationListParams import com.courier.models.notifications.NotificationListResponse import com.courier.models.notifications.NotificationListVersionsParams @@ -162,6 +163,50 @@ interface NotificationServiceAsync { fun archive(id: String, requestOptions: RequestOptions): CompletableFuture = archive(id, NotificationArchiveParams.none(), requestOptions) + /** + * Duplicate a notification template. Creates a standalone copy within the same workspace and + * environment, with " COPY" appended to the title. The copy clones the source draft's tags, + * brand, subscription topic, routing strategy, channels, and content, and is always created as + * a standalone template (it is not linked to any journey or broadcast, even if the source was). + * Templates that are scoped to a journey or a broadcast cannot be duplicated through this + * endpoint. + */ + fun duplicate(id: String): CompletableFuture = + duplicate(id, NotificationDuplicateParams.none()) + + /** @see duplicate */ + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + duplicate(params.toBuilder().id(id).build(), requestOptions) + + /** @see duplicate */ + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + ): CompletableFuture = + duplicate(id, params, RequestOptions.none()) + + /** @see duplicate */ + fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + + /** @see duplicate */ + fun duplicate( + params: NotificationDuplicateParams + ): CompletableFuture = duplicate(params, RequestOptions.none()) + + /** @see duplicate */ + fun duplicate( + id: String, + requestOptions: RequestOptions, + ): CompletableFuture = + duplicate(id, NotificationDuplicateParams.none(), requestOptions) + /** List versions of a notification template. */ fun listVersions(id: String): CompletableFuture = listVersions(id, NotificationListVersionsParams.none()) @@ -542,6 +587,49 @@ interface NotificationServiceAsync { fun archive(id: String, requestOptions: RequestOptions): CompletableFuture = archive(id, NotificationArchiveParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `post /notifications/{id}/duplicate`, but is otherwise + * the same as [NotificationServiceAsync.duplicate]. + */ + fun duplicate( + id: String + ): CompletableFuture> = + duplicate(id, NotificationDuplicateParams.none()) + + /** @see duplicate */ + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + duplicate(params.toBuilder().id(id).build(), requestOptions) + + /** @see duplicate */ + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + ): CompletableFuture> = + duplicate(id, params, RequestOptions.none()) + + /** @see duplicate */ + fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> + + /** @see duplicate */ + fun duplicate( + params: NotificationDuplicateParams + ): CompletableFuture> = + duplicate(params, RequestOptions.none()) + + /** @see duplicate */ + fun duplicate( + id: String, + requestOptions: RequestOptions, + ): CompletableFuture> = + duplicate(id, NotificationDuplicateParams.none(), requestOptions) + /** * Returns a raw HTTP response for `get /notifications/{id}/versions`, but is otherwise the * same as [NotificationServiceAsync.listVersions]. diff --git a/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsyncImpl.kt b/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsyncImpl.kt index dd19c526..b0f46fb9 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsyncImpl.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/async/NotificationServiceAsyncImpl.kt @@ -20,6 +20,7 @@ import com.courier.core.prepareAsync import com.courier.models.notifications.NotificationArchiveParams import com.courier.models.notifications.NotificationContentMutationResponse import com.courier.models.notifications.NotificationCreateParams +import com.courier.models.notifications.NotificationDuplicateParams import com.courier.models.notifications.NotificationListParams import com.courier.models.notifications.NotificationListResponse import com.courier.models.notifications.NotificationListVersionsParams @@ -83,6 +84,13 @@ class NotificationServiceAsyncImpl internal constructor(private val clientOption // delete /notifications/{id} withRawResponse().archive(params, requestOptions).thenAccept {} + override fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // post /notifications/{id}/duplicate + withRawResponse().duplicate(params, requestOptions).thenApply { it.parse() } + override fun listVersions( params: NotificationListVersionsParams, requestOptions: RequestOptions, @@ -272,6 +280,40 @@ class NotificationServiceAsyncImpl internal constructor(private val clientOption } } + private val duplicateHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions, + ): CompletableFuture> { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("id", params.id().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("notifications", params._pathParam(0), "duplicate") + .apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } } + .build() + .prepareAsync(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + return request + .thenComposeAsync { clientOptions.httpClient.executeAsync(it, requestOptions) } + .thenApply { response -> + errorHandler.handle(response).parseable { + response + .use { duplicateHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + } + private val listVersionsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationService.kt b/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationService.kt index e2b77916..85f2a049 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationService.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationService.kt @@ -9,6 +9,7 @@ import com.courier.core.http.HttpResponseFor import com.courier.models.notifications.NotificationArchiveParams import com.courier.models.notifications.NotificationContentMutationResponse import com.courier.models.notifications.NotificationCreateParams +import com.courier.models.notifications.NotificationDuplicateParams import com.courier.models.notifications.NotificationListParams import com.courier.models.notifications.NotificationListResponse import com.courier.models.notifications.NotificationListVersionsParams @@ -153,6 +154,44 @@ interface NotificationService { fun archive(id: String, requestOptions: RequestOptions) = archive(id, NotificationArchiveParams.none(), requestOptions) + /** + * Duplicate a notification template. Creates a standalone copy within the same workspace and + * environment, with " COPY" appended to the title. The copy clones the source draft's tags, + * brand, subscription topic, routing strategy, channels, and content, and is always created as + * a standalone template (it is not linked to any journey or broadcast, even if the source was). + * Templates that are scoped to a journey or a broadcast cannot be duplicated through this + * endpoint. + */ + fun duplicate(id: String): NotificationTemplateResponse = + duplicate(id, NotificationDuplicateParams.none()) + + /** @see duplicate */ + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): NotificationTemplateResponse = duplicate(params.toBuilder().id(id).build(), requestOptions) + + /** @see duplicate */ + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + ): NotificationTemplateResponse = duplicate(id, params, RequestOptions.none()) + + /** @see duplicate */ + fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): NotificationTemplateResponse + + /** @see duplicate */ + fun duplicate(params: NotificationDuplicateParams): NotificationTemplateResponse = + duplicate(params, RequestOptions.none()) + + /** @see duplicate */ + fun duplicate(id: String, requestOptions: RequestOptions): NotificationTemplateResponse = + duplicate(id, NotificationDuplicateParams.none(), requestOptions) + /** List versions of a notification template. */ fun listVersions(id: String): NotificationTemplateVersionListResponse = listVersions(id, NotificationListVersionsParams.none()) @@ -523,6 +562,52 @@ interface NotificationService { fun archive(id: String, requestOptions: RequestOptions): HttpResponse = archive(id, NotificationArchiveParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `post /notifications/{id}/duplicate`, but is otherwise + * the same as [NotificationService.duplicate]. + */ + @MustBeClosed + fun duplicate(id: String): HttpResponseFor = + duplicate(id, NotificationDuplicateParams.none()) + + /** @see duplicate */ + @MustBeClosed + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + duplicate(params.toBuilder().id(id).build(), requestOptions) + + /** @see duplicate */ + @MustBeClosed + fun duplicate( + id: String, + params: NotificationDuplicateParams = NotificationDuplicateParams.none(), + ): HttpResponseFor = + duplicate(id, params, RequestOptions.none()) + + /** @see duplicate */ + @MustBeClosed + fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor + + /** @see duplicate */ + @MustBeClosed + fun duplicate( + params: NotificationDuplicateParams + ): HttpResponseFor = duplicate(params, RequestOptions.none()) + + /** @see duplicate */ + @MustBeClosed + fun duplicate( + id: String, + requestOptions: RequestOptions, + ): HttpResponseFor = + duplicate(id, NotificationDuplicateParams.none(), requestOptions) + /** * Returns a raw HTTP response for `get /notifications/{id}/versions`, but is otherwise the * same as [NotificationService.listVersions]. diff --git a/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationServiceImpl.kt b/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationServiceImpl.kt index 52ae5eb6..9b6d39da 100644 --- a/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationServiceImpl.kt +++ b/courier-java-core/src/main/kotlin/com/courier/services/blocking/NotificationServiceImpl.kt @@ -20,6 +20,7 @@ import com.courier.core.prepare import com.courier.models.notifications.NotificationArchiveParams import com.courier.models.notifications.NotificationContentMutationResponse import com.courier.models.notifications.NotificationCreateParams +import com.courier.models.notifications.NotificationDuplicateParams import com.courier.models.notifications.NotificationListParams import com.courier.models.notifications.NotificationListResponse import com.courier.models.notifications.NotificationListVersionsParams @@ -80,6 +81,13 @@ class NotificationServiceImpl internal constructor(private val clientOptions: Cl withRawResponse().archive(params, requestOptions) } + override fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions, + ): NotificationTemplateResponse = + // post /notifications/{id}/duplicate + withRawResponse().duplicate(params, requestOptions).parse() + override fun listVersions( params: NotificationListVersionsParams, requestOptions: RequestOptions, @@ -255,6 +263,37 @@ class NotificationServiceImpl internal constructor(private val clientOptions: Cl } } + private val duplicateHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun duplicate( + params: NotificationDuplicateParams, + requestOptions: RequestOptions, + ): HttpResponseFor { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("id", params.id().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("notifications", params._pathParam(0), "duplicate") + .apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } } + .build() + .prepare(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + val response = clientOptions.httpClient.execute(request, requestOptions) + return errorHandler.handle(response).parseable { + response + .use { duplicateHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + private val listVersionsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationDuplicateParamsTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationDuplicateParamsTest.kt new file mode 100644 index 00000000..48c3c0e3 --- /dev/null +++ b/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationDuplicateParamsTest.kt @@ -0,0 +1,23 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.courier.models.notifications + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class NotificationDuplicateParamsTest { + + @Test + fun create() { + NotificationDuplicateParams.builder().id("id").build() + } + + @Test + fun pathParams() { + val params = NotificationDuplicateParams.builder().id("id").build() + + assertThat(params._pathParam(0)).isEqualTo("id") + // out-of-bound path param + assertThat(params._pathParam(1)).isEqualTo("") + } +} diff --git a/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationTemplateSummaryTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationTemplateSummaryTest.kt index b03e82bf..df730f63 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationTemplateSummaryTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/notifications/NotificationTemplateSummaryTest.kt @@ -19,6 +19,7 @@ internal class NotificationTemplateSummaryTest { .name("name") .state(NotificationTemplateSummary.State.DRAFT) .addTag("string") + .subscriptionTopicId("subscription_topic_id") .updated(0L) .updater("updater") .build() @@ -30,6 +31,8 @@ internal class NotificationTemplateSummaryTest { assertThat(notificationTemplateSummary.state()) .isEqualTo(NotificationTemplateSummary.State.DRAFT) assertThat(notificationTemplateSummary.tags()).containsExactly("string") + assertThat(notificationTemplateSummary.subscriptionTopicId()) + .contains("subscription_topic_id") assertThat(notificationTemplateSummary.updated()).contains(0L) assertThat(notificationTemplateSummary.updater()).contains("updater") } @@ -45,6 +48,7 @@ internal class NotificationTemplateSummaryTest { .name("name") .state(NotificationTemplateSummary.State.DRAFT) .addTag("string") + .subscriptionTopicId("subscription_topic_id") .updated(0L) .updater("updater") .build() diff --git a/courier-java-core/src/test/kotlin/com/courier/models/routingstrategies/AssociatedNotificationListResponseTest.kt b/courier-java-core/src/test/kotlin/com/courier/models/routingstrategies/AssociatedNotificationListResponseTest.kt index 92ed3f51..fc48cf1a 100644 --- a/courier-java-core/src/test/kotlin/com/courier/models/routingstrategies/AssociatedNotificationListResponseTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/models/routingstrategies/AssociatedNotificationListResponseTest.kt @@ -24,6 +24,7 @@ internal class AssociatedNotificationListResponseTest { .name("name") .state(NotificationTemplateSummary.State.DRAFT) .addTag("string") + .subscriptionTopicId("subscription_topic_id") .updated(0L) .updater("updater") .build() @@ -41,6 +42,7 @@ internal class AssociatedNotificationListResponseTest { .name("name") .state(NotificationTemplateSummary.State.DRAFT) .addTag("string") + .subscriptionTopicId("subscription_topic_id") .updated(0L) .updater("updater") .build() @@ -61,6 +63,7 @@ internal class AssociatedNotificationListResponseTest { .name("name") .state(NotificationTemplateSummary.State.DRAFT) .addTag("string") + .subscriptionTopicId("subscription_topic_id") .updated(0L) .updater("updater") .build() diff --git a/courier-java-core/src/test/kotlin/com/courier/services/async/NotificationServiceAsyncTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/async/NotificationServiceAsyncTest.kt index a103ee60..b06be93b 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/async/NotificationServiceAsyncTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/async/NotificationServiceAsyncTest.kt @@ -122,6 +122,18 @@ internal class NotificationServiceAsyncTest { val response = future.get() } + @Disabled("Mock server tests are disabled") + @Test + fun duplicate() { + val client = CourierOkHttpClientAsync.builder().apiKey("My API Key").build() + val notificationServiceAsync = client.notifications() + + val notificationTemplateResponseFuture = notificationServiceAsync.duplicate("id") + + val notificationTemplateResponse = notificationTemplateResponseFuture.get() + notificationTemplateResponse.validate() + } + @Disabled("Mock server tests are disabled") @Test fun listVersions() { diff --git a/courier-java-core/src/test/kotlin/com/courier/services/blocking/NotificationServiceTest.kt b/courier-java-core/src/test/kotlin/com/courier/services/blocking/NotificationServiceTest.kt index 1f525e00..4d405dcb 100644 --- a/courier-java-core/src/test/kotlin/com/courier/services/blocking/NotificationServiceTest.kt +++ b/courier-java-core/src/test/kotlin/com/courier/services/blocking/NotificationServiceTest.kt @@ -117,6 +117,17 @@ internal class NotificationServiceTest { notificationService.archive("id") } + @Disabled("Mock server tests are disabled") + @Test + fun duplicate() { + val client = CourierOkHttpClient.builder().apiKey("My API Key").build() + val notificationService = client.notifications() + + val notificationTemplateResponse = notificationService.duplicate("id") + + notificationTemplateResponse.validate() + } + @Disabled("Mock server tests are disabled") @Test fun listVersions() {