diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 210fc7f4..84d8fb1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/courier-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: @@ -37,7 +37,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/courier-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1363fe01..8f918e4b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.11.2" + ".": "5.12.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 9e3b99b6..44174988 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-b03f5346718dcd7b22f15893445ea593ab87535c90c540fc5a796c6ecd54d779.yml +openapi_spec_hash: 15677d3be4ecd33eab154080b4bc437f +config_hash: dcd58abf1e56b114bdce22336ca3d570 diff --git a/CHANGELOG.md b/CHANGELOG.md index c9bf1084..5b31308a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 5.12.0 (2026-07-20) + +Full Changelog: [v5.11.2...v5.12.0](https://github.com/trycourier/courier-php/compare/v5.11.2...v5.12.0) + +### Features + +* Document POST /notifications/{id}/duplicate [C-18607] ([#165](https://github.com/trycourier/courier-php/issues/165)) ([be965ad](https://github.com/trycourier/courier-php/commit/be965adeb3c022bd988e03a976d2842634726a5c)) +* Merge pull request [#149](https://github.com/trycourier/courier-php/issues/149) from trycourier/geraldosilva/c-19201-notification-template-subscription-topic-id ([622c65e](https://github.com/trycourier/courier-php/commit/622c65e6334f628d60e5c3aa433bc37b38eb3619)) +* Merge pull request [#168](https://github.com/trycourier/courier-php/issues/168) from trycourier/geraldosilva/c-19419-topic-id-notification-template-summary ([9f7f082](https://github.com/trycourier/courier-php/commit/9f7f082b3f2f87033f9d1242aed96ebcdf141485)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([69bfa6b](https://github.com/trycourier/courier-php/commit/69bfa6b0e807f5120d48dcc1cc7aa1f532ec0a9c)) + + +### Bug Fixes + +* **ci:** drop Packagist secret requirement from release doctor ([#90](https://github.com/trycourier/courier-php/issues/90)) ([e0b992d](https://github.com/trycourier/courier-php/commit/e0b992df2ac46603779dc3c566dd178c558f42c4)) + ## 5.11.2 (2026-07-10) Full Changelog: [v5.11.1...v5.11.2](https://github.com/trycourier/courier-php/compare/v5.11.1...v5.11.2) diff --git a/src/Notifications/NotificationTemplateSummary.php b/src/Notifications/NotificationTemplateSummary.php index 3a97bb81..d94a3aee 100644 --- a/src/Notifications/NotificationTemplateSummary.php +++ b/src/Notifications/NotificationTemplateSummary.php @@ -20,6 +20,8 @@ * name: string, * state: State|value-of, * tags: list, + * subscriptionTopicID?: string|null, + * topicID?: string|null, * updated?: int|null, * updater?: string|null, * } @@ -55,6 +57,18 @@ final class NotificationTemplateSummary implements BaseModel #[Required(list: 'string')] public array $tags; + /** + * The linked subscription (preference) topic of the published version. Omitted when no topic is linked or the template has never been published. + */ + #[Optional('subscription_topic_id')] + public ?string $subscriptionTopicID; + + /** + * Alias of subscription_topic_id, provided under the same name V1 list items use for the linked topic. Always carries the same value as subscription_topic_id. + */ + #[Optional('topic_id')] + public ?string $topicID; + /** * Epoch milliseconds of last update. */ @@ -109,6 +123,8 @@ public static function with( string $name, State|string $state, array $tags, + ?string $subscriptionTopicID = null, + ?string $topicID = null, ?int $updated = null, ?string $updater = null, ): self { @@ -121,6 +137,8 @@ public static function with( $self['state'] = $state; $self['tags'] = $tags; + null !== $subscriptionTopicID && $self['subscriptionTopicID'] = $subscriptionTopicID; + null !== $topicID && $self['topicID'] = $topicID; null !== $updated && $self['updated'] = $updated; null !== $updater && $self['updater'] = $updater; @@ -187,6 +205,28 @@ public function withTags(array $tags): self return $self; } + /** + * The linked subscription (preference) topic of the published version. Omitted when no topic is linked or the template has never been published. + */ + public function withSubscriptionTopicID(string $subscriptionTopicID): self + { + $self = clone $this; + $self['subscriptionTopicID'] = $subscriptionTopicID; + + return $self; + } + + /** + * Alias of subscription_topic_id, provided under the same name V1 list items use for the linked topic. Always carries the same value as subscription_topic_id. + */ + public function withTopicID(string $topicID): self + { + $self = clone $this; + $self['topicID'] = $topicID; + + return $self; + } + /** * Epoch milliseconds of last update. */ diff --git a/src/ServiceContracts/NotificationsContract.php b/src/ServiceContracts/NotificationsContract.php index 50c98c5e..eed714d6 100644 --- a/src/ServiceContracts/NotificationsContract.php +++ b/src/ServiceContracts/NotificationsContract.php @@ -86,6 +86,19 @@ public function archive( RequestOptions|array|null $requestOptions = null ): mixed; + /** + * @api + * + * @param string $id template ID (nt_ prefix) + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function duplicate( + string $id, + RequestOptions|array|null $requestOptions = null + ): NotificationTemplateResponse; + /** * @api * diff --git a/src/ServiceContracts/NotificationsRawContract.php b/src/ServiceContracts/NotificationsRawContract.php index cfbb01c3..af9cee75 100644 --- a/src/ServiceContracts/NotificationsRawContract.php +++ b/src/ServiceContracts/NotificationsRawContract.php @@ -91,6 +91,21 @@ public function archive( RequestOptions|array|null $requestOptions = null ): BaseResponse; + /** + * @api + * + * @param string $id template ID (nt_ prefix) + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function duplicate( + string $id, + RequestOptions|array|null $requestOptions = null + ): BaseResponse; + /** * @api * diff --git a/src/Services/NotificationsRawService.php b/src/Services/NotificationsRawService.php index 8fd428f6..1eed20cb 100644 --- a/src/Services/NotificationsRawService.php +++ b/src/Services/NotificationsRawService.php @@ -172,6 +172,31 @@ public function archive( ); } + /** + * @api + * + * 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. + * + * @param string $id template ID (nt_ prefix) + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function duplicate( + string $id, + RequestOptions|array|null $requestOptions = null + ): BaseResponse { + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'post', + path: ['notifications/%1$s/duplicate', $id], + options: $requestOptions, + convert: NotificationTemplateResponse::class, + ); + } + /** * @api * diff --git a/src/Services/NotificationsService.php b/src/Services/NotificationsService.php index 534e97a9..36d1d733 100644 --- a/src/Services/NotificationsService.php +++ b/src/Services/NotificationsService.php @@ -147,6 +147,26 @@ public function archive( return $response->parse(); } + /** + * @api + * + * 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. + * + * @param string $id template ID (nt_ prefix) + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function duplicate( + string $id, + RequestOptions|array|null $requestOptions = null + ): NotificationTemplateResponse { + // @phpstan-ignore-next-line argument.type + $response = $this->raw->duplicate($id, requestOptions: $requestOptions); + + return $response->parse(); + } + /** * @api * diff --git a/src/Version.php b/src/Version.php index 256e7687..2f86aeaa 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace Courier; // x-release-please-start-version -const VERSION = '5.11.2'; +const VERSION = '5.12.0'; // x-release-please-end diff --git a/tests/Services/NotificationsTest.php b/tests/Services/NotificationsTest.php index 5e9ce3c9..01964184 100644 --- a/tests/Services/NotificationsTest.php +++ b/tests/Services/NotificationsTest.php @@ -118,6 +118,19 @@ public function testArchive(): void $this->assertNull($result); } + #[Test] + public function testDuplicate(): void + { + if (UnsupportedMockTests::$skip) { + $this->markTestSkipped('Mock server tests are disabled'); + } + + $result = $this->client->notifications->duplicate('id'); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(NotificationTemplateResponse::class, $result); + } + #[Test] public function testListVersions(): void {