diff --git a/src/main/java/com/crowdin/client/Client.java b/src/main/java/com/crowdin/client/Client.java index 6145770a..a2776b5b 100644 --- a/src/main/java/com/crowdin/client/Client.java +++ b/src/main/java/com/crowdin/client/Client.java @@ -27,6 +27,7 @@ import com.crowdin.client.stringcomments.StringCommentsApi; import com.crowdin.client.stringcorrections.StringCorrectionsApi; import com.crowdin.client.stringtranslations.StringTranslationsApi; +import com.crowdin.client.styleguide.StyleGuidesApi; import com.crowdin.client.tasks.TasksApi; import com.crowdin.client.teams.TeamsApi; import com.crowdin.client.translationmemory.TranslationMemoryApi; @@ -76,6 +77,7 @@ public class Client extends CrowdinApi { private final BranchesApi branchesApi; private final AIApi aiApi; private final StringCorrectionsApi stringCorrectionsApi; + private final StyleGuidesApi styleGuidesApi; public Client(Credentials credentials) { super(credentials); @@ -113,6 +115,7 @@ public Client(Credentials credentials) { this.branchesApi = new BranchesApi(credentials); this.aiApi = new AIApi(credentials); this.stringCorrectionsApi = new StringCorrectionsApi(credentials); + this.styleGuidesApi = new StyleGuidesApi(credentials); } public Client(Credentials credentials, ClientConfig clientConfig) { @@ -151,6 +154,7 @@ public Client(Credentials credentials, ClientConfig clientConfig) { this.branchesApi = new BranchesApi(credentials, clientConfig); this.aiApi = new AIApi(credentials, clientConfig); this.stringCorrectionsApi = new StringCorrectionsApi(credentials, clientConfig); + this.styleGuidesApi = new StyleGuidesApi(credentials, clientConfig); } } diff --git a/src/main/java/com/crowdin/client/styleguide/StyleGuidesApi.java b/src/main/java/com/crowdin/client/styleguide/StyleGuidesApi.java new file mode 100644 index 00000000..abdd384c --- /dev/null +++ b/src/main/java/com/crowdin/client/styleguide/StyleGuidesApi.java @@ -0,0 +1,128 @@ +package com.crowdin.client.styleguide; + +import com.crowdin.client.core.CrowdinApi; +import com.crowdin.client.core.http.HttpRequestConfig; +import com.crowdin.client.core.http.exceptions.HttpBadRequestException; +import com.crowdin.client.core.http.exceptions.HttpException; +import com.crowdin.client.core.model.*; +import com.crowdin.client.styleguide.model.*; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class StyleGuidesApi extends CrowdinApi { + public StyleGuidesApi(Credentials credentials) { + super(credentials); + } + + public StyleGuidesApi(Credentials credentials, ClientConfig clientConfig) { + super(credentials, clientConfig); + } + + /** + * @param styleGuideId style guide identifier + * @see + */ + public void deleteStyleGuide(Long styleGuideId) throws HttpException, HttpBadRequestException { + this.httpClient.delete(this.url + "/style-guides/" + styleGuideId, new HttpRequestConfig(), Void.class); + } + + /** + * @param styleGuideId style guide identifier + * @param request request object + * @return updated style guide + * @see + */ + public ResponseObject editStyleGuide(Long styleGuideId, List request) throws HttpException, HttpBadRequestException { + StyleGuideResponseObject styleGuideResponseObject = this.httpClient.patch(this.url + "/style-guides/" + styleGuideId, request, new HttpRequestConfig(), StyleGuideResponseObject.class); + return ResponseObject.of(styleGuideResponseObject.getData()); + } + + /** + * @param styleGuideId style guide identifier + * @return style guide + * @see + */ + public ResponseObject getStyleGuide(Long styleGuideId) throws HttpException, HttpBadRequestException { + StyleGuideResponseObject styleGuideResponseObject = this.httpClient.get(this.url + "/style-guides/" + styleGuideId, new HttpRequestConfig(), StyleGuideResponseObject.class); + return ResponseObject.of(styleGuideResponseObject.getData()); + } + + /** + * @param request request object + * @return newly created style guide + * @see + */ + public ResponseObject addStyleGuide(AddStyleGuideRequest request) throws HttpException, HttpBadRequestException { + StyleGuideResponseObject styleGuideResponseObject = this.httpClient.post(this.url + "/style-guides", request, new HttpRequestConfig(), StyleGuideResponseObject.class); + return ResponseObject.of(styleGuideResponseObject.getData()); + } + + /** + * @param userId user identifier + * @param limit maximum number of items to retrieve (default 25) + * @param offset starting offset in the collection (default 0) + * @return list of style guides + * @see + */ + public ResponseList listStyleGuides(Long userId, Integer limit, Integer offset) throws HttpException, HttpBadRequestException { + ListStyleGuidesParams params = new ListStyleGuidesParams(); + params.setUserId(userId); + params.setLimit(limit); + params.setOffset(offset); + return listStyleGuides(params); + } + + /** + * @param userId user identifier + * @param limit maximum number of items to retrieve (default 25) + * @param offset starting offset in the collection (default 0) + * @param orderBy list of OrderByField + * @return list of style guides + * @see + */ + public ResponseList listStyleGuides(Long userId, Integer limit, Integer offset, List orderBy) throws HttpException, HttpBadRequestException { + ListStyleGuidesParams params = new ListStyleGuidesParams(); + params.setUserId(userId); + params.setLimit(limit); + params.setOffset(offset); + params.setOrderByList(orderBy); + return listStyleGuides(params); + } + + public ResponseList listStyleGuides(ListStyleGuidesParams params) throws HttpException, HttpBadRequestException { + ListStyleGuidesParams query = Optional.ofNullable(params).orElse(new ListStyleGuidesParams()); + + String orderBy = query.getOrderByList() != null + ? OrderByField.generateSortParam(query.getOrderByList()) + : query.getOrderBy(); + + Map> queryParams = HttpRequestConfig.buildUrlParams( + "userId", Optional.ofNullable(query.getUserId()), + "limit", Optional.ofNullable(query.getLimit()), + "offset", Optional.ofNullable(query.getOffset()), + "orderBy", Optional.ofNullable(orderBy) + ); + StyleGuideResponseList styleGuideResponseList = this.httpClient.get(this.url + "/style-guides", new HttpRequestConfig(queryParams), StyleGuideResponseList.class); + return StyleGuideResponseList.to(styleGuideResponseList); + } +} diff --git a/src/main/java/com/crowdin/client/styleguide/model/AddStyleGuideRequest.java b/src/main/java/com/crowdin/client/styleguide/model/AddStyleGuideRequest.java new file mode 100644 index 00000000..6e883308 --- /dev/null +++ b/src/main/java/com/crowdin/client/styleguide/model/AddStyleGuideRequest.java @@ -0,0 +1,15 @@ +package com.crowdin.client.styleguide.model; + +import lombok.Data; + +import java.util.List; + +@Data +public class AddStyleGuideRequest { + private String name; + private String aiInstructions; + private List languageIds; + private List projectIds; + private Boolean isShared; + private Long storageId; +} diff --git a/src/main/java/com/crowdin/client/styleguide/model/ListStyleGuidesParams.java b/src/main/java/com/crowdin/client/styleguide/model/ListStyleGuidesParams.java new file mode 100644 index 00000000..ed3a776d --- /dev/null +++ b/src/main/java/com/crowdin/client/styleguide/model/ListStyleGuidesParams.java @@ -0,0 +1,14 @@ +package com.crowdin.client.styleguide.model; + +import com.crowdin.client.core.model.OrderByField; +import com.crowdin.client.core.model.Pagination; +import lombok.Data; + +import java.util.List; + +@Data +public class ListStyleGuidesParams extends Pagination { + private Long userId; + private String orderBy; + private List orderByList; +} diff --git a/src/main/java/com/crowdin/client/styleguide/model/StyleGuide.java b/src/main/java/com/crowdin/client/styleguide/model/StyleGuide.java new file mode 100644 index 00000000..4fc5bf45 --- /dev/null +++ b/src/main/java/com/crowdin/client/styleguide/model/StyleGuide.java @@ -0,0 +1,21 @@ +package com.crowdin.client.styleguide.model; + +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class StyleGuide { + private Long id; + private String name; + private String aiInstructions; + private Long userId; + private List languageIds; + private List projectIds; + private Boolean isShared; + private String webUrl; + private String downloadLink; + private Date createdAt; + private Date updatedAt; +} diff --git a/src/main/java/com/crowdin/client/styleguide/model/StyleGuideResponseList.java b/src/main/java/com/crowdin/client/styleguide/model/StyleGuideResponseList.java new file mode 100644 index 00000000..daccb9c3 --- /dev/null +++ b/src/main/java/com/crowdin/client/styleguide/model/StyleGuideResponseList.java @@ -0,0 +1,25 @@ +package com.crowdin.client.styleguide.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 StyleGuideResponseList { + private List data; + private Pagination pagination; + + public static ResponseList to(StyleGuideResponseList styleGuideResponseList) { + return ResponseList.of( + styleGuideResponseList.getData().stream() + .map(StyleGuideResponseObject::getData) + .map(ResponseObject::of) + .collect(Collectors.toList()), + styleGuideResponseList.getPagination() + ); + } +} diff --git a/src/main/java/com/crowdin/client/styleguide/model/StyleGuideResponseObject.java b/src/main/java/com/crowdin/client/styleguide/model/StyleGuideResponseObject.java new file mode 100644 index 00000000..b00e7dc2 --- /dev/null +++ b/src/main/java/com/crowdin/client/styleguide/model/StyleGuideResponseObject.java @@ -0,0 +1,8 @@ +package com.crowdin.client.styleguide.model; + +import lombok.Data; + +@Data +public class StyleGuideResponseObject { + private StyleGuide data; +} diff --git a/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiStyleGuidesOrderByIdAscTest.java b/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiStyleGuidesOrderByIdAscTest.java new file mode 100644 index 00000000..c00c1e32 --- /dev/null +++ b/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiStyleGuidesOrderByIdAscTest.java @@ -0,0 +1,55 @@ +package com.crowdin.client.styleguide; + +import com.crowdin.client.core.model.OrderByField; +import com.crowdin.client.core.model.ResponseList; +import com.crowdin.client.core.model.SortOrder; +import com.crowdin.client.framework.RequestMock; +import com.crowdin.client.framework.TestClient; +import com.crowdin.client.styleguide.model.StyleGuide; +import org.apache.http.client.methods.HttpGet; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StyleGuidesApiStyleGuidesOrderByIdAscTest extends TestClient { + + private final Long styleGuideId = 2L; + private final Long styleGuide2Id = 3L; + + @Override + public List getMocks() { + return Arrays.asList( + RequestMock.build(this.url + "/style-guides", HttpGet.METHOD_NAME, "api/styleguide/listStyleGuidesOrderByIdAsc.json", new HashMap() {{ + put("orderBy", "id%20asc"); + }}) + ); + } + + @Test + public void listStyleGuidesTest_orderByIdNull() { + OrderByField orderById = new OrderByField(); + orderById.setFieldName("id"); + + ResponseList styleGuideResponseList = this.getStyleGuidesApi().listStyleGuides(null, null, null, singletonList(orderById)); + assertEquals(2, styleGuideResponseList.getData().size()); + assertEquals(styleGuideId, styleGuideResponseList.getData().get(0).getData().getId()); + assertEquals(styleGuide2Id, styleGuideResponseList.getData().get(1).getData().getId()); + } + + @Test + public void listStyleGuidesTest_orderByIdAsc() { + OrderByField orderById = new OrderByField(); + orderById.setFieldName("id"); + orderById.setOrderBy(SortOrder.ASC); + + ResponseList styleGuideResponseList = this.getStyleGuidesApi().listStyleGuides(null, null, null, singletonList(orderById)); + assertEquals(2, styleGuideResponseList.getData().size()); + assertEquals(styleGuideId, styleGuideResponseList.getData().get(0).getData().getId()); + assertEquals(styleGuide2Id, styleGuideResponseList.getData().get(1).getData().getId()); + } +} diff --git a/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiStyleGuidesOrderByIdDescTest.java b/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiStyleGuidesOrderByIdDescTest.java new file mode 100644 index 00000000..e31ed5e2 --- /dev/null +++ b/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiStyleGuidesOrderByIdDescTest.java @@ -0,0 +1,44 @@ +package com.crowdin.client.styleguide; + +import com.crowdin.client.core.model.OrderByField; +import com.crowdin.client.core.model.ResponseList; +import com.crowdin.client.core.model.SortOrder; +import com.crowdin.client.framework.RequestMock; +import com.crowdin.client.framework.TestClient; +import com.crowdin.client.styleguide.model.StyleGuide; +import org.apache.http.client.methods.HttpGet; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StyleGuidesApiStyleGuidesOrderByIdDescTest extends TestClient { + + private final Long styleGuideId = 2L; + private final Long styleGuide2Id = 3L; + + @Override + public List getMocks() { + return Arrays.asList( + RequestMock.build(this.url + "/style-guides", HttpGet.METHOD_NAME, "api/styleguide/listStyleGuidesOrderByIdDesc.json", new HashMap() {{ + put("orderBy", "id%20desc"); + }}) + ); + } + + @Test + public void listStyleGuidesTest_orderByIdDesc() { + OrderByField orderById = new OrderByField(); + orderById.setFieldName("id"); + orderById.setOrderBy(SortOrder.DESC); + + ResponseList styleGuideResponseList = this.getStyleGuidesApi().listStyleGuides(null, null, null, singletonList(orderById)); + assertEquals(2, styleGuideResponseList.getData().size()); + assertEquals(styleGuide2Id, styleGuideResponseList.getData().get(0).getData().getId()); + assertEquals(styleGuideId, styleGuideResponseList.getData().get(1).getData().getId()); + } +} diff --git a/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiTest.java b/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiTest.java new file mode 100644 index 00000000..ddf25ec4 --- /dev/null +++ b/src/test/java/com/crowdin/client/styleguide/StyleGuidesApiTest.java @@ -0,0 +1,104 @@ +package com.crowdin.client.styleguide; + +import com.crowdin.client.core.model.PatchOperation; +import com.crowdin.client.core.model.PatchRequest; +import com.crowdin.client.core.model.ResponseList; +import com.crowdin.client.core.model.ResponseObject; +import com.crowdin.client.framework.RequestMock; +import com.crowdin.client.framework.TestClient; +import com.crowdin.client.styleguide.model.AddStyleGuideRequest; +import com.crowdin.client.styleguide.model.StyleGuide; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StyleGuidesApiTest extends TestClient { + + private final Long styleGuideId = 2L; + private final Long storageId = 1L; + private final String name = "Be My Eyes iOS's Style Guide"; + private final String aiInstructions = "string"; + private final List languageIds = Arrays.asList("uk", "fr", "de"); + private final List projectIds = Arrays.asList(1L, 2L, 3L); + private final Boolean isShared = false; + + @Override + public List getMocks() { + return Arrays.asList( + RequestMock.build(this.url + "/style-guides", HttpGet.METHOD_NAME, "api/styleguide/listStyleGuides.json"), + RequestMock.build(this.url + "/style-guides", HttpPost.METHOD_NAME, "api/styleguide/addStyleGuideRequest.json", "api/styleguide/styleGuide.json"), + RequestMock.build(this.url + "/style-guides/" + styleGuideId, HttpGet.METHOD_NAME, "api/styleguide/styleGuide.json"), + RequestMock.build(this.url + "/style-guides/" + styleGuideId, HttpDelete.METHOD_NAME), + RequestMock.build(this.url + "/style-guides/" + styleGuideId, HttpPatch.METHOD_NAME, "api/styleguide/editStyleGuide.json", "api/styleguide/styleGuide.json") + ); + } + + @Test + public void listStyleGuidesTest() { + ResponseList styleGuideResponseList = this.getStyleGuidesApi().listStyleGuides(null, null, null); + assertEquals(styleGuideResponseList.getData().size(), 1); + assertEquals(styleGuideResponseList.getData().get(0).getData().getId(), styleGuideId); + assertEquals(styleGuideResponseList.getData().get(0).getData().getName(), name); + assertEquals(styleGuideResponseList.getData().get(0).getData().getAiInstructions(), aiInstructions); + assertEquals(styleGuideResponseList.getData().get(0).getData().getIsShared(), isShared); + assertEquals(styleGuideResponseList.getData().get(0).getData().getLanguageIds(), languageIds); + assertEquals(styleGuideResponseList.getData().get(0).getData().getProjectIds(), projectIds); + } + + @Test + public void addStyleGuideTest() { + AddStyleGuideRequest request = new AddStyleGuideRequest(); + request.setName(name); + request.setAiInstructions(aiInstructions); + request.setLanguageIds(languageIds); + request.setProjectIds(projectIds); + request.setIsShared(isShared); + request.setStorageId(storageId); + ResponseObject styleGuideResponseObject = this.getStyleGuidesApi().addStyleGuide(request); + assertEquals(styleGuideResponseObject.getData().getId(), styleGuideId); + assertEquals(styleGuideResponseObject.getData().getName(), name); + assertEquals(styleGuideResponseObject.getData().getAiInstructions(), aiInstructions); + assertEquals(styleGuideResponseObject.getData().getIsShared(), isShared); + assertEquals(styleGuideResponseObject.getData().getLanguageIds(), languageIds); + assertEquals(styleGuideResponseObject.getData().getProjectIds(), projectIds); + } + + @Test + public void getStyleGuideTest() { + ResponseObject styleGuideResponseObject = this.getStyleGuidesApi().getStyleGuide(styleGuideId); + assertEquals(styleGuideResponseObject.getData().getId(), styleGuideId); + assertEquals(styleGuideResponseObject.getData().getName(), name); + assertEquals(styleGuideResponseObject.getData().getAiInstructions(), aiInstructions); + assertEquals(styleGuideResponseObject.getData().getIsShared(), isShared); + assertEquals(styleGuideResponseObject.getData().getLanguageIds(), languageIds); + assertEquals(styleGuideResponseObject.getData().getProjectIds(), projectIds); + } + + @Test + public void deleteStyleGuideTest() { + this.getStyleGuidesApi().deleteStyleGuide(styleGuideId); + } + + @Test + public void editStyleGuideTest() { + PatchRequest request = new PatchRequest(); + request.setOp(PatchOperation.REPLACE); + request.setValue(name); + request.setPath("/name"); + ResponseObject styleGuideResponseObject = this.getStyleGuidesApi().editStyleGuide(styleGuideId, singletonList(request)); + assertEquals(styleGuideResponseObject.getData().getId(), styleGuideId); + assertEquals(styleGuideResponseObject.getData().getName(), name); + assertEquals(styleGuideResponseObject.getData().getAiInstructions(), aiInstructions); + assertEquals(styleGuideResponseObject.getData().getIsShared(), isShared); + assertEquals(styleGuideResponseObject.getData().getLanguageIds(), languageIds); + assertEquals(styleGuideResponseObject.getData().getProjectIds(), projectIds); + } +} diff --git a/src/test/resources/api/styleguide/addStyleGuideRequest.json b/src/test/resources/api/styleguide/addStyleGuideRequest.json new file mode 100644 index 00000000..7d7384a6 --- /dev/null +++ b/src/test/resources/api/styleguide/addStyleGuideRequest.json @@ -0,0 +1,8 @@ +{ + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "languageIds": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "storageId": 1 +} diff --git a/src/test/resources/api/styleguide/editStyleGuide.json b/src/test/resources/api/styleguide/editStyleGuide.json new file mode 100644 index 00000000..25b6dacc --- /dev/null +++ b/src/test/resources/api/styleguide/editStyleGuide.json @@ -0,0 +1,7 @@ +[ + { + "op": "replace", + "path": "/name", + "value": "Be My Eyes iOS's Style Guide" + } +] diff --git a/src/test/resources/api/styleguide/listStyleGuides.json b/src/test/resources/api/styleguide/listStyleGuides.json new file mode 100644 index 00000000..a4ab3698 --- /dev/null +++ b/src/test/resources/api/styleguide/listStyleGuides.json @@ -0,0 +1,20 @@ +{ + "data": [ + { + "data": { + "id": 2, + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "userId": 2, + "languageIds": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "createdAt": "2019-09-16T13:42:04+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } +} diff --git a/src/test/resources/api/styleguide/listStyleGuidesOrderByIdAsc.json b/src/test/resources/api/styleguide/listStyleGuidesOrderByIdAsc.json new file mode 100644 index 00000000..40b000ac --- /dev/null +++ b/src/test/resources/api/styleguide/listStyleGuidesOrderByIdAsc.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "data": { + "id": 2, + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "userId": 2, + "languageId": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "createdAt": "2019-09-16T13:42:04+00:00" + } + }, + { + "data": { + "id": 3, + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "userId": 2, + "languageId": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "createdAt": "2019-09-16T13:42:04+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } +} diff --git a/src/test/resources/api/styleguide/listStyleGuidesOrderByIdDesc.json b/src/test/resources/api/styleguide/listStyleGuidesOrderByIdDesc.json new file mode 100644 index 00000000..026bc469 --- /dev/null +++ b/src/test/resources/api/styleguide/listStyleGuidesOrderByIdDesc.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "data": { + "id": 3, + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "userId": 2, + "languageId": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "createdAt": "2019-09-16T13:42:04+00:00" + } + }, + { + "data": { + "id": 2, + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "userId": 2, + "languageId": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "createdAt": "2019-09-16T13:42:04+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } +} diff --git a/src/test/resources/api/styleguide/styleGuide.json b/src/test/resources/api/styleguide/styleGuide.json new file mode 100644 index 00000000..e4ba6963 --- /dev/null +++ b/src/test/resources/api/styleguide/styleGuide.json @@ -0,0 +1,12 @@ +{ + "data": { + "id": 2, + "name": "Be My Eyes iOS's Style Guide", + "aiInstructions": "string", + "userId": 2, + "languageIds": ["uk", "fr", "de"], + "projectIds": [1, 2, 3], + "isShared": false, + "createdAt": "2019-09-16T13:42:04+00:00" + } +}