From f08b0c9e8e46903d5fe839fa1dc5f34c5128fcc8 Mon Sep 17 00:00:00 2001 From: Anikesh348 Date: Thu, 3 Sep 2026 16:04:07 +0000 Subject: [PATCH 1/2] feat: add support for Application Consents API Adds listApplicationConsents, addApplicationConsent, editApplicationConsent, and deleteApplicationConsent methods to ApplicationsApi, covering the crowdin.com-only /applications/consents endpoints for managing user consent decisions granted or denied to applications. Closes: #393 --- .../client/applications/ApplicationsApi.java | 63 +++++++++++++++++++ .../model/AddApplicationConsentRequest.java | 13 ++++ .../consents/model/ApplicationConsent.java | 26 ++++++++ .../model/ApplicationConsentResponseList.java | 26 ++++++++ .../ApplicationConsentResponseObject.java | 9 +++ .../consents/model/ConsentStatus.java | 16 +++++ .../model/ListApplicationConsentsParams.java | 15 +++++ .../applications/ApplicationsApiTest.java | 47 +++++++++++++- .../addApplicationConsentRequest.json | 9 +++ .../api/applications/applicationConsent.json | 20 ++++++ .../editApplicationConsentRequest.json | 7 +++ .../listApplicationConsentsResponse.json | 28 +++++++++ 12 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/crowdin/client/applications/consents/model/AddApplicationConsentRequest.java create mode 100644 src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsent.java create mode 100644 src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseList.java create mode 100644 src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseObject.java create mode 100644 src/main/java/com/crowdin/client/applications/consents/model/ConsentStatus.java create mode 100644 src/main/java/com/crowdin/client/applications/consents/model/ListApplicationConsentsParams.java create mode 100644 src/test/resources/api/applications/addApplicationConsentRequest.json create mode 100644 src/test/resources/api/applications/applicationConsent.json create mode 100644 src/test/resources/api/applications/editApplicationConsentRequest.json create mode 100644 src/test/resources/api/applications/listApplicationConsentsResponse.json diff --git a/src/main/java/com/crowdin/client/applications/ApplicationsApi.java b/src/main/java/com/crowdin/client/applications/ApplicationsApi.java index fdfb32af8..845e965cd 100644 --- a/src/main/java/com/crowdin/client/applications/ApplicationsApi.java +++ b/src/main/java/com/crowdin/client/applications/ApplicationsApi.java @@ -1,5 +1,10 @@ package com.crowdin.client.applications; +import com.crowdin.client.applications.consents.model.AddApplicationConsentRequest; +import com.crowdin.client.applications.consents.model.ApplicationConsent; +import com.crowdin.client.applications.consents.model.ApplicationConsentResponseList; +import com.crowdin.client.applications.consents.model.ApplicationConsentResponseObject; +import com.crowdin.client.applications.consents.model.ListApplicationConsentsParams; import com.crowdin.client.applications.installations.model.ApplicationInstallation; import com.crowdin.client.applications.installations.model.ApplicationInstallationResponseList; import com.crowdin.client.applications.installations.model.ApplicationInstallationResponseObject; @@ -180,4 +185,62 @@ public ResponseObject editApplicationInstallation(Strin ApplicationInstallationResponseObject response = this.httpClient.patch(builtUrl, request, new HttpRequestConfig(), ApplicationInstallationResponseObject.class); return ResponseObject.of(response.getData()); } + + /** + * @param params filter, sorting and pagination params + * @return list of application consents + * @see + */ + public ResponseList listApplicationConsents(ListApplicationConsentsParams params) throws HttpException, HttpBadRequestException { + ListApplicationConsentsParams query = Optional.ofNullable(params).orElse(new ListApplicationConsentsParams()); + Map> queryParams = HttpRequestConfig.buildUrlParams( + "identifier", Optional.ofNullable(query.getIdentifier()), + "orderBy", Optional.ofNullable(OrderByField.generateSortParam(query.getOrderBy())), + "limit", Optional.ofNullable(query.getLimit()), + "offset", Optional.ofNullable(query.getOffset()) + ); + String builtUrl = String.format("%s/applications/consents", this.url); + ApplicationConsentResponseList response = this.httpClient.get(builtUrl, new HttpRequestConfig(queryParams), ApplicationConsentResponseList.class); + return ApplicationConsentResponseList.to(response); + } + + /** + * @param request request object + * @return newly created application consent + * @see + */ + public ResponseObject addApplicationConsent(AddApplicationConsentRequest request) throws HttpException, HttpBadRequestException { + String builtUrl = String.format("%s/applications/consents", this.url); + ApplicationConsentResponseObject response = this.httpClient.post(builtUrl, request, new HttpRequestConfig(), ApplicationConsentResponseObject.class); + return ResponseObject.of(response.getData()); + } + + /** + * @param consentId consent decision identifier + * @param request request object + * @return updated application consent + * @see + */ + public ResponseObject editApplicationConsent(Long consentId, List request) throws HttpException, HttpBadRequestException { + String builtUrl = String.format("%s/applications/consents/%s", this.url, consentId); + ApplicationConsentResponseObject response = this.httpClient.patch(builtUrl, request, new HttpRequestConfig(), ApplicationConsentResponseObject.class); + return ResponseObject.of(response.getData()); + } + + /** + * @param consentId consent decision identifier + * @see + */ + public void deleteApplicationConsent(Long consentId) throws HttpException, HttpBadRequestException { + String builtUrl = String.format("%s/applications/consents/%s", this.url, consentId); + this.httpClient.delete(builtUrl, new HttpRequestConfig(), Void.class); + } } diff --git a/src/main/java/com/crowdin/client/applications/consents/model/AddApplicationConsentRequest.java b/src/main/java/com/crowdin/client/applications/consents/model/AddApplicationConsentRequest.java new file mode 100644 index 000000000..bd599bc86 --- /dev/null +++ b/src/main/java/com/crowdin/client/applications/consents/model/AddApplicationConsentRequest.java @@ -0,0 +1,13 @@ +package com.crowdin.client.applications.consents.model; + +import lombok.Data; + +import java.util.List; + +@Data +public class AddApplicationConsentRequest { + private String identifier; + private Long installedBy; + private ConsentStatus status; + private List scopes; +} diff --git a/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsent.java b/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsent.java new file mode 100644 index 000000000..0d14890e0 --- /dev/null +++ b/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsent.java @@ -0,0 +1,26 @@ +package com.crowdin.client.applications.consents.model; + +import lombok.Data; + +import java.util.Date; + +@Data +public class ApplicationConsent { + + private Long id; + private User installedBy; + private String identifier; + private String name; + private ConsentStatus status; + private String[] scopes; + private Date createdAt; + private Date updatedAt; + + @Data + public static class User { + private Long id; + private String username; + private String fullName; + private String avatarUrl; + } +} diff --git a/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseList.java b/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseList.java new file mode 100644 index 000000000..06a1517d4 --- /dev/null +++ b/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseList.java @@ -0,0 +1,26 @@ +package com.crowdin.client.applications.consents.model; + +import com.crowdin.client.core.model.Pagination; +import com.crowdin.client.core.model.ResponseList; +import com.crowdin.client.core.model.ResponseObject; +import lombok.Data; + +import java.util.List; +import java.util.stream.Collectors; + +@Data +public class ApplicationConsentResponseList { + + private List data; + private Pagination pagination; + + public static ResponseList to(ApplicationConsentResponseList applicationConsentResponseList) { + return ResponseList.of( + applicationConsentResponseList.getData().stream() + .map(ApplicationConsentResponseObject::getData) + .map(ResponseObject::of) + .collect(Collectors.toList()), + applicationConsentResponseList.getPagination() + ); + } +} diff --git a/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseObject.java b/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseObject.java new file mode 100644 index 000000000..6964238e1 --- /dev/null +++ b/src/main/java/com/crowdin/client/applications/consents/model/ApplicationConsentResponseObject.java @@ -0,0 +1,9 @@ +package com.crowdin.client.applications.consents.model; + +import lombok.Data; + +@Data +public class ApplicationConsentResponseObject { + + private ApplicationConsent data; +} diff --git a/src/main/java/com/crowdin/client/applications/consents/model/ConsentStatus.java b/src/main/java/com/crowdin/client/applications/consents/model/ConsentStatus.java new file mode 100644 index 000000000..4ef98d770 --- /dev/null +++ b/src/main/java/com/crowdin/client/applications/consents/model/ConsentStatus.java @@ -0,0 +1,16 @@ +package com.crowdin.client.applications.consents.model; + +import com.crowdin.client.core.model.EnumConverter; + +public enum ConsentStatus implements EnumConverter { + GRANTED, DENIED; + + public static ConsentStatus from(String value) { + return ConsentStatus.valueOf(value.toUpperCase()); + } + + @Override + public String to(ConsentStatus v) { + return v.name().toLowerCase(); + } +} diff --git a/src/main/java/com/crowdin/client/applications/consents/model/ListApplicationConsentsParams.java b/src/main/java/com/crowdin/client/applications/consents/model/ListApplicationConsentsParams.java new file mode 100644 index 000000000..9175130f0 --- /dev/null +++ b/src/main/java/com/crowdin/client/applications/consents/model/ListApplicationConsentsParams.java @@ -0,0 +1,15 @@ +package com.crowdin.client.applications.consents.model; + +import com.crowdin.client.core.model.OrderByField; +import com.crowdin.client.core.model.Pagination; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ListApplicationConsentsParams extends Pagination { + private String identifier; + private List orderBy; +} diff --git a/src/test/java/com/crowdin/client/applications/ApplicationsApiTest.java b/src/test/java/com/crowdin/client/applications/ApplicationsApiTest.java index baedf3727..e58b65619 100644 --- a/src/test/java/com/crowdin/client/applications/ApplicationsApiTest.java +++ b/src/test/java/com/crowdin/client/applications/ApplicationsApiTest.java @@ -1,5 +1,6 @@ package com.crowdin.client.applications; +import com.crowdin.client.applications.consents.model.*; import com.crowdin.client.applications.installations.model.*; import com.crowdin.client.core.model.PatchOperation; import com.crowdin.client.core.model.PatchRequest; @@ -25,6 +26,8 @@ class ApplicationsApiTest extends TestClient { private final String path = "path"; private final String builtUrl = this.url + "/applications/" + applicationIdentifier + "/api/" + path; private final String builtUrlInstallation = this.url + "/applications/installations"; + private final String builtUrlConsents = this.url + "/applications/consents"; + private final Long consentId = 12L; @Override public List getMocks() { @@ -38,7 +41,11 @@ public List getMocks() { RequestMock.build(builtUrlInstallation + "/" + applicationIdentifier, HttpDelete.METHOD_NAME), RequestMock.build(builtUrlInstallation + "/" + applicationIdentifier, HttpPatch.METHOD_NAME, "api/applications/editApplicationInstallation.json", "api/applications/applicationInstallation.json"), RequestMock.build(builtUrlInstallation, HttpGet.METHOD_NAME, "api/applications/installApplicationList.json"), - RequestMock.build(builtUrlInstallation, HttpPost.METHOD_NAME, "api/applications/installApplication.json", "api/applications/applicationInstallation.json") + RequestMock.build(builtUrlInstallation, HttpPost.METHOD_NAME, "api/applications/installApplication.json", "api/applications/applicationInstallation.json"), + RequestMock.build(builtUrlConsents, HttpGet.METHOD_NAME, "api/applications/listApplicationConsentsResponse.json"), + RequestMock.build(builtUrlConsents, HttpPost.METHOD_NAME, "api/applications/addApplicationConsentRequest.json", "api/applications/applicationConsent.json"), + RequestMock.build(builtUrlConsents + "/" + consentId, HttpPatch.METHOD_NAME, "api/applications/editApplicationConsentRequest.json", "api/applications/applicationConsent.json"), + RequestMock.build(builtUrlConsents + "/" + consentId, HttpDelete.METHOD_NAME) ); } @@ -137,4 +144,42 @@ void editApplicationInstallation() { assertEquals(response.getData().getIdentifier(), "example-application"); assertEquals(response.getData().getName(), "Application name"); } + + @Test + void listApplicationConsents() { + ResponseList responseList = this.getApplicationsApi().listApplicationConsents(null); + assertNotNull(responseList); + assertEquals(1, responseList.getData().size()); + assertEquals(responseList.getData().get(0).getData().getIdentifier(), "example-application"); + assertEquals(responseList.getData().get(0).getData().getStatus(), ConsentStatus.GRANTED); + } + + @Test + void addApplicationConsent() { + AddApplicationConsentRequest request = new AddApplicationConsentRequest(); + request.setIdentifier("example-application"); + request.setInstalledBy(consentId); + request.setStatus(ConsentStatus.GRANTED); + request.setScopes(Arrays.asList("project", "tm")); + ResponseObject response = this.getApplicationsApi().addApplicationConsent(request); + assertNotNull(response); + assertEquals(response.getData().getId(), consentId); + assertEquals(response.getData().getStatus(), ConsentStatus.GRANTED); + } + + @Test + void editApplicationConsent() { + PatchRequest request = new PatchRequest(); + request.setOp(PatchOperation.REPLACE); + request.setPath("/status"); + request.setValue("denied"); + ResponseObject response = this.getApplicationsApi().editApplicationConsent(consentId, singletonList(request)); + assertNotNull(response); + assertEquals(response.getData().getId(), consentId); + } + + @Test + void deleteApplicationConsent() { + this.getApplicationsApi().deleteApplicationConsent(consentId); + } } diff --git a/src/test/resources/api/applications/addApplicationConsentRequest.json b/src/test/resources/api/applications/addApplicationConsentRequest.json new file mode 100644 index 000000000..d635638fd --- /dev/null +++ b/src/test/resources/api/applications/addApplicationConsentRequest.json @@ -0,0 +1,9 @@ +{ + "identifier": "example-application", + "installedBy": 12, + "status": "granted", + "scopes": [ + "project", + "tm" + ] +} diff --git a/src/test/resources/api/applications/applicationConsent.json b/src/test/resources/api/applications/applicationConsent.json new file mode 100644 index 000000000..c3152bfec --- /dev/null +++ b/src/test/resources/api/applications/applicationConsent.json @@ -0,0 +1,20 @@ +{ + "data": { + "id": 12, + "installedBy": { + "id": 12, + "username": "john_smith", + "fullName": "John Smith", + "avatarUrl": "" + }, + "identifier": "example-application", + "name": "My App", + "status": "granted", + "scopes": [ + "project", + "tm" + ], + "createdAt": "2026-07-24T10:00:00+00:00", + "updatedAt": "2026-07-24T10:00:00+00:00" + } +} diff --git a/src/test/resources/api/applications/editApplicationConsentRequest.json b/src/test/resources/api/applications/editApplicationConsentRequest.json new file mode 100644 index 000000000..0050d8491 --- /dev/null +++ b/src/test/resources/api/applications/editApplicationConsentRequest.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/status", + "value": "denied" + } +] diff --git a/src/test/resources/api/applications/listApplicationConsentsResponse.json b/src/test/resources/api/applications/listApplicationConsentsResponse.json new file mode 100644 index 000000000..954c2c829 --- /dev/null +++ b/src/test/resources/api/applications/listApplicationConsentsResponse.json @@ -0,0 +1,28 @@ +{ + "data": [ + { + "data": { + "id": 12, + "installedBy": { + "id": 12, + "username": "john_smith", + "fullName": "John Smith", + "avatarUrl": "" + }, + "identifier": "example-application", + "name": "My App", + "status": "granted", + "scopes": [ + "project", + "tm" + ], + "createdAt": "2026-07-24T10:00:00+00:00", + "updatedAt": "2026-07-24T10:00:00+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } +} From aad2a9d8f038101f4bb9c01e40c7c0b8cdc23ba5 Mon Sep 17 00:00:00 2001 From: Anikesh348 Date: Fri, 4 Sep 2026 05:20:48 +0000 Subject: [PATCH 2/2] docs: add Enterprise API links for Application Consents methods Match the existing ApplicationsApi Javadoc pattern. These endpoints are available on both crowdin.com and Crowdin Enterprise. --- .../java/com/crowdin/client/applications/ApplicationsApi.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/crowdin/client/applications/ApplicationsApi.java b/src/main/java/com/crowdin/client/applications/ApplicationsApi.java index 845e965cd..62d666cef 100644 --- a/src/main/java/com/crowdin/client/applications/ApplicationsApi.java +++ b/src/main/java/com/crowdin/client/applications/ApplicationsApi.java @@ -191,6 +191,7 @@ public ResponseObject editApplicationInstallation(Strin * @return list of application consents * @see */ public ResponseList listApplicationConsents(ListApplicationConsentsParams params) throws HttpException, HttpBadRequestException { @@ -211,6 +212,7 @@ public ResponseList listApplicationConsents(ListApplicationC * @return newly created application consent * @see */ public ResponseObject addApplicationConsent(AddApplicationConsentRequest request) throws HttpException, HttpBadRequestException { @@ -225,6 +227,7 @@ public ResponseObject addApplicationConsent(AddApplicationCo * @return updated application consent * @see */ public ResponseObject editApplicationConsent(Long consentId, List request) throws HttpException, HttpBadRequestException { @@ -237,6 +240,7 @@ public ResponseObject editApplicationConsent(Long consentId, * @param consentId consent decision identifier * @see */ public void deleteApplicationConsent(Long consentId) throws HttpException, HttpBadRequestException {