diff --git a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java index b4a1fdc6b..fc38104f4 100644 --- a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java +++ b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java @@ -7,6 +7,7 @@ import com.crowdin.client.labels.model.AddLabelRequest; import com.crowdin.client.labels.model.Label; import com.crowdin.client.languages.model.Language; +import com.crowdin.client.projectsgroups.model.AddProjectRequest; import com.crowdin.client.projectsgroups.model.Project; import com.crowdin.client.projectsgroups.model.ProjectSettings; import com.crowdin.client.projectsgroups.model.Type; @@ -522,4 +523,11 @@ public List listProjects() { return executeRequestFullList((limit, offset) -> this.client.getProjectsGroupsApi() .listProjects(null, 1, limit, offset)); } + + @Override + public Project addProject(AddProjectRequest request) { + return executeRequest(() -> this.client.getProjectsGroupsApi() + .addProject(request) + .getData()); + } } diff --git a/src/main/java/com/crowdin/cli/client/ProjectClient.java b/src/main/java/com/crowdin/cli/client/ProjectClient.java index d659e3e20..67517451c 100644 --- a/src/main/java/com/crowdin/cli/client/ProjectClient.java +++ b/src/main/java/com/crowdin/cli/client/ProjectClient.java @@ -5,6 +5,7 @@ import com.crowdin.client.labels.model.AddLabelRequest; import com.crowdin.client.labels.model.Label; import com.crowdin.client.languages.model.Language; +import com.crowdin.client.projectsgroups.model.AddProjectRequest; import com.crowdin.client.projectsgroups.model.Project; import com.crowdin.client.sourcefiles.model.*; import com.crowdin.client.sourcestrings.model.*; @@ -126,4 +127,6 @@ default CrowdinProjectFull downloadFullProject() { String getProjectUrl(); List listProjects(); + + Project addProject(AddProjectRequest request); } diff --git a/src/main/java/com/crowdin/cli/commands/Actions.java b/src/main/java/com/crowdin/cli/commands/Actions.java index 4deb2063a..706a1ed3c 100644 --- a/src/main/java/com/crowdin/cli/commands/Actions.java +++ b/src/main/java/com/crowdin/cli/commands/Actions.java @@ -147,4 +147,6 @@ NewAction preTranslate( NewAction projectBrowse(); NewAction projectList(boolean isVerbose); + + NewAction projectAdd(String name, boolean isStringBased, String sourceLanguage, List languages, boolean isPublic, boolean plainView); } diff --git a/src/main/java/com/crowdin/cli/commands/actions/CliActions.java b/src/main/java/com/crowdin/cli/commands/actions/CliActions.java index 287058bd2..0e45b101b 100644 --- a/src/main/java/com/crowdin/cli/commands/actions/CliActions.java +++ b/src/main/java/com/crowdin/cli/commands/actions/CliActions.java @@ -318,4 +318,9 @@ public NewAction projectBrowse() { public NewAction projectList(boolean isVerbose) { return new ProjectListAction(isVerbose); } + + @Override + public NewAction projectAdd(String name, boolean isStringBased, String sourceLanguage, List languages, boolean isPublic, boolean plainView) { + return new ProjectAddAction(name, isStringBased, sourceLanguage, languages, isPublic, plainView); + } } diff --git a/src/main/java/com/crowdin/cli/commands/actions/ProjectAddAction.java b/src/main/java/com/crowdin/cli/commands/actions/ProjectAddAction.java new file mode 100644 index 000000000..c18cd653d --- /dev/null +++ b/src/main/java/com/crowdin/cli/commands/actions/ProjectAddAction.java @@ -0,0 +1,46 @@ +package com.crowdin.cli.commands.actions; + +import com.crowdin.cli.client.ProjectClient; +import com.crowdin.cli.commands.NewAction; +import com.crowdin.cli.commands.Outputter; +import com.crowdin.cli.commands.functionality.PropertiesBeanUtils; +import com.crowdin.cli.commands.functionality.RequestBuilder; +import com.crowdin.cli.properties.ProjectProperties; +import com.crowdin.cli.utils.console.ExecutionStatus; +import com.crowdin.client.projectsgroups.model.AddProjectRequest; +import com.crowdin.client.projectsgroups.model.Project; +import com.crowdin.client.projectsgroups.model.Visibility; +import lombok.AllArgsConstructor; + +import java.util.List; +import java.util.Objects; + +import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE; + +@AllArgsConstructor +class ProjectAddAction implements NewAction { + + private final String name; + private final boolean isStringBased; + private final String sourceLanguage; + private final List languages; + private final boolean isPublic; + private final boolean plainView; + + @Override + public void act(Outputter out, ProjectProperties properties, ProjectClient client) { + boolean isOrganization = PropertiesBeanUtils.isOrganization(properties.getBaseUrl()); + String sourceLang = Objects.nonNull(sourceLanguage) ? sourceLanguage : "en"; + Visibility visibility = isOrganization ? null : isPublic ? Visibility.OPEN : Visibility.PRIVATE; + + AddProjectRequest request = RequestBuilder.addProject(name, isStringBased, sourceLang, languages, visibility, isOrganization); + Project project = client.addProject(request); + if (!plainView) { + out.println(ExecutionStatus.OK.withIcon( + String.format(RESOURCE_BUNDLE.getString("message.project.list"), project.getId(), project.getName()) + )); + } else { + out.println(project.getId().toString()); + } + } +} diff --git a/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java b/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java index 7b5d2e78e..2f977db14 100644 --- a/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java +++ b/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java @@ -10,6 +10,7 @@ import com.crowdin.client.glossaries.model.GlossariesFormat; import com.crowdin.client.glossaries.model.ImportGlossaryRequest; import com.crowdin.client.labels.model.AddLabelRequest; +import com.crowdin.client.projectsgroups.model.*; import com.crowdin.client.sourcefiles.model.AddBranchRequest; import com.crowdin.client.sourcestrings.model.*; import com.crowdin.client.stringcomments.model.AddStringCommentRequest; @@ -22,10 +23,7 @@ import com.crowdin.client.translationmemory.model.TranslationMemoryImportRequest; import com.crowdin.client.translations.model.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; public class RequestBuilder { @@ -325,4 +323,23 @@ private static PluralText buildPluralText(String text, String one, String two, S Optional.ofNullable(zero).ifPresent(pluralText::setZero); return pluralText; } + + public static AddProjectRequest addProject(String name, boolean isStringBased, String sourceLanguage, List languages, Visibility visibility, boolean isOrganization) { + if (isStringBased) { + StringsBasedProjectRequest request = new StringsBasedProjectRequest(); + request.setName(name); + request.setType(com.crowdin.client.projectsgroups.model.Type.STRINGS_BASED); + request.setSourceLanguageId(sourceLanguage); + request.setTargetLanguageIds(languages); + Optional.ofNullable(visibility).ifPresent(v -> request.setVisibility(v.toString())); + return request; + } else { + FilesBasedProjectRequest request = new FilesBasedProjectRequest(); + request.setName(name); + request.setSourceLanguageId(sourceLanguage); + request.setTargetLanguageIds(languages); + Optional.ofNullable(visibility).ifPresent(v -> request.setVisibility(v.toString())); + return request; + } + } } diff --git a/src/main/java/com/crowdin/cli/commands/picocli/ProjectAddSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/ProjectAddSubcommand.java new file mode 100644 index 000000000..9b13fc362 --- /dev/null +++ b/src/main/java/com/crowdin/cli/commands/picocli/ProjectAddSubcommand.java @@ -0,0 +1,41 @@ +package com.crowdin.cli.commands.picocli; + +import com.crowdin.cli.client.ProjectClient; +import com.crowdin.cli.commands.Actions; +import com.crowdin.cli.commands.NewAction; +import com.crowdin.cli.properties.ProjectProperties; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.util.List; + +@Command( + name = CommandNames.ADD, + sortOptions = false +) +class ProjectAddSubcommand extends ActCommandProject { + + @Parameters(descriptionKey = "crowdin.project.add.name") + protected String name; + + @Option(names = {"-l", "--language"}, required = true, paramLabel = "...", order = -2, descriptionKey = "crowdin.project.add.language") + protected List languages; + + @Option(names = {"--source-language"}, paramLabel = "...", order = -2, descriptionKey = "crowdin.project.add.source-language") + protected String sourceLanguage; + + @Option(names = {"--public"}, order = -2, descriptionKey = "crowdin.project.add.public") + protected boolean isPublic = false; + + @Option(names = {"--string-based"}, order = -2, descriptionKey = "crowdin.project.add.string-based") + protected boolean isStringBased = false; + + @Option(names = {"--plain"}, descriptionKey = "crowdin.list.usage.plain") + protected boolean plainView; + + @Override + protected NewAction getAction(Actions actions) { + return actions.projectAdd(this.name, this.isStringBased, this.sourceLanguage, this.languages, this.isPublic, this.plainView); + } +} diff --git a/src/main/java/com/crowdin/cli/commands/picocli/ProjectSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/ProjectSubcommand.java index dcce6f06c..ee79a88fd 100644 --- a/src/main/java/com/crowdin/cli/commands/picocli/ProjectSubcommand.java +++ b/src/main/java/com/crowdin/cli/commands/picocli/ProjectSubcommand.java @@ -6,7 +6,8 @@ name = CommandNames.PROJECT, subcommands = { ProjectBrowseSubcommand.class, - ProjectListSubcommand.class + ProjectListSubcommand.class, + ProjectAddSubcommand.class } ) class ProjectSubcommand extends HelpCommand { diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties index 7f6bef7a8..4feaae4a0 100755 --- a/src/main/resources/messages/messages.properties +++ b/src/main/resources/messages/messages.properties @@ -464,6 +464,15 @@ crowdin.project.browse.usage.customSynopsis=@|fg(green) crowdin project browse|@ crowdin.project.list.usage.description=List projects with manager access crowdin.project.list.usage.customSynopsis=@|fg(green) crowdin project list|@ [CONFIG OPTIONS] [OPTIONS] +# CROWDIN PROJECT ADD +crowdin.project.add.usage.description=Add a new project +crowdin.project.add.usage.customSynopsis=@|fg(green) crowdin project add|@ [CONFIG OPTIONS] [OPTIONS] +crowdin.project.add.name=Project name +crowdin.project.add.language=Target language identifier. Can be specified multiple times +crowdin.project.add.source-language=Defines the source language. English by default +crowdin.project.add.public=Defines whether the project is public. Private by default +crowdin.project.add.string-based=Defines whether the project is string-based + error.collect_project_info=Failed to collect project info. Please contact our support team for help error.no_sources=No sources found for '%s' pattern. Check the source paths in your configuration file error.only_enterprise=Operation is available only for Crowdin Enterprise diff --git a/src/test/java/com/crowdin/cli/client/CrowdinProjectClientTest.java b/src/test/java/com/crowdin/cli/client/CrowdinProjectClientTest.java index a76e1630c..0cde8e368 100644 --- a/src/test/java/com/crowdin/cli/client/CrowdinProjectClientTest.java +++ b/src/test/java/com/crowdin/cli/client/CrowdinProjectClientTest.java @@ -11,6 +11,7 @@ import com.crowdin.client.core.model.PatchRequest; import com.crowdin.client.languages.model.LanguageResponseList; import com.crowdin.client.languages.model.LanguageResponseObject; +import com.crowdin.client.projectsgroups.model.AddProjectRequest; import com.crowdin.client.projectsgroups.model.Project; import com.crowdin.client.projectsgroups.model.ProjectResponseObject; import com.crowdin.client.projectsgroups.model.ProjectSettings; @@ -120,6 +121,8 @@ public class CrowdinProjectClientTest { String.format("%s/projects/%d/strings/%d", url, projectId, stringId); private static final String editSourceStringUrl = String.format("%s/projects/%d/strings/%d", url, projectId, stringId); + private static final String addProjectUrl = + String.format("%s/projects", url); @BeforeEach public void init() { @@ -552,6 +555,20 @@ public void testEditSourceString() { verifyNoMoreInteractions(httpClientMock); } + @Test + public void testAddProject() { + AddProjectRequest request = new AddProjectRequest(); + ProjectResponseObject response = new ProjectResponseObject() {{ + setData(new Project()); + }}; + when(httpClientMock.post(eq(addProjectUrl), eq(request), any(), eq(ProjectResponseObject.class))) + .thenReturn(response); + + client.addProject(request); + + verify(httpClientMock).post(eq(addProjectUrl), eq(request), any(), eq(ProjectResponseObject.class)); + verifyNoMoreInteractions(httpClientMock); + } @Test diff --git a/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java b/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java index f4a387bef..9ffb8cbec 100644 --- a/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java +++ b/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java @@ -191,4 +191,9 @@ void testFileDelete() { void testBranchClone() { assertNotNull(actions.branchClone(null, null, false, false)); } + + @Test + void testProjectAdd() { + assertNotNull(actions.projectAdd(null, false, null, null, false, false)); + } } diff --git a/src/test/java/com/crowdin/cli/commands/actions/ProjectAddActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/ProjectAddActionTest.java new file mode 100644 index 000000000..b730dc819 --- /dev/null +++ b/src/test/java/com/crowdin/cli/commands/actions/ProjectAddActionTest.java @@ -0,0 +1,87 @@ +package com.crowdin.cli.commands.actions; + +import com.crowdin.cli.client.ProjectClient; +import com.crowdin.cli.commands.NewAction; +import com.crowdin.cli.commands.Outputter; +import com.crowdin.cli.properties.ProjectProperties; +import com.crowdin.client.projectsgroups.model.*; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.*; + +class ProjectAddActionTest { + + private ProjectClient client = mock(ProjectClient.class); + private NewAction action; + private ProjectProperties properties = new ProjectProperties(); + private static final String PROJECT_TITLE = "project"; + private static final List TARGET_LANGUAGE = List.of("uk"); + + @Test + public void testProjectAdd_FileBased() { + properties.setBaseUrl("https://api.crowdin.com"); + Project project = new Project(); + project.setName(PROJECT_TITLE); + project.setId(1L); + + FilesBasedProjectRequest request = new FilesBasedProjectRequest(); + request.setName(PROJECT_TITLE); + request.setTargetLanguageIds(TARGET_LANGUAGE); + request.setSourceLanguageId("en"); + request.setVisibility("PRIVATE"); + + when(client.addProject(request)).thenReturn(project); + + action = new ProjectAddAction(PROJECT_TITLE, false, null, TARGET_LANGUAGE, false, false); + action.act(Outputter.getDefault(), properties, client); + + verify(client).addProject(request); + verifyNoMoreInteractions(client); + } + + @Test + public void testProjectAdd_StringBased() { + properties.setBaseUrl("https://api.crowdin.com"); + Project project = new Project(); + project.setName(PROJECT_TITLE); + project.setId(1L); + + StringsBasedProjectRequest request = new StringsBasedProjectRequest(); + request.setName(PROJECT_TITLE); + request.setType(Type.STRINGS_BASED); + request.setTargetLanguageIds(TARGET_LANGUAGE); + request.setSourceLanguageId("fr"); + request.setVisibility("OPEN"); + + when(client.addProject(request)).thenReturn(project); + + action = new ProjectAddAction(PROJECT_TITLE, true, "fr", TARGET_LANGUAGE, true, false); + action.act(Outputter.getDefault(), properties, client); + + verify(client).addProject(request); + verifyNoMoreInteractions(client); + } + + @Test + public void testProjectAdd_Enterprise() { + properties.setBaseUrl("https://companyname.crowdin.com"); + Project project = new Project(); + project.setName(PROJECT_TITLE); + project.setId(1L); + + FilesBasedProjectRequest request = new FilesBasedProjectRequest(); + request.setName(PROJECT_TITLE); + request.setTargetLanguageIds(TARGET_LANGUAGE); + request.setSourceLanguageId("fr"); + + when(client.addProject(request)).thenReturn(project); + + action = new ProjectAddAction(PROJECT_TITLE, false, "fr", TARGET_LANGUAGE, true, true); + action.act(Outputter.getDefault(), properties, client); + + verify(client).addProject(request); + verifyNoMoreInteractions(client); + } +} diff --git a/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java b/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java index 03e128b0e..7f87d52d8 100644 --- a/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java +++ b/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java @@ -138,6 +138,7 @@ void mockActions() { .thenReturn(actionMock); when(actionsMock.projectBrowse()).thenReturn(actionMock); when(actionsMock.projectList(anyBoolean())).thenReturn(actionMock); + when(actionsMock.projectAdd(any(), anyBoolean(), any(), any(), anyBoolean(), anyBoolean())).thenReturn(actionMock); when(actionsMock.branchClone(any(), any(), anyBoolean(), anyBoolean())) .thenReturn(actionMock); when(actionsMock.branchMerge(any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())) diff --git a/src/test/java/com/crowdin/cli/commands/picocli/ProjectAddSubcommandTest.java b/src/test/java/com/crowdin/cli/commands/picocli/ProjectAddSubcommandTest.java new file mode 100644 index 000000000..9fcc98266 --- /dev/null +++ b/src/test/java/com/crowdin/cli/commands/picocli/ProjectAddSubcommandTest.java @@ -0,0 +1,22 @@ +package com.crowdin.cli.commands.picocli; + +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.verify; + +class ProjectAddSubcommandTest extends PicocliTestUtils { + + @Test + public void testProjectAddInvalidOptions() { + this.executeInvalidParams(CommandNames.PROJECT, CommandNames.ADD); + } + + @Test + public void testProjectAdd() { + this.execute(CommandNames.PROJECT, CommandNames.ADD, "name", "--language", "uk"); + verify(actionsMock).projectAdd(any(), anyBoolean(), any(), any(), anyBoolean(), anyBoolean()); + this.check(true); + } +} diff --git a/website/mantemplates/crowdin-project-add.adoc b/website/mantemplates/crowdin-project-add.adoc new file mode 100644 index 000000000..262a25402 --- /dev/null +++ b/website/mantemplates/crowdin-project-add.adoc @@ -0,0 +1,16 @@ +:includedir: ../generated-picocli-docs +:command: crowdin-project-add + +== crowdin project add + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-description] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-synopsis] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-arguments] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-commands] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-options] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-footer] diff --git a/website/sidebars.js b/website/sidebars.js index a60daa106..d95a6da21 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -161,6 +161,7 @@ const sidebars = { collapsed: true, items: [ 'commands/crowdin-project-list', + 'commands/crowdin-project-add', 'commands/crowdin-project-browse', ] },