From be61e056f5c0afed1383dea38464eaf0e179f0b5 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Thu, 29 Jan 2026 11:27:28 -0700 Subject: [PATCH 1/2] feat: allow project to org assignment --- .../org/justserve/cli/JustServeCommand.java | 7 +-- .../cli/command/AssignOrgToProject.java | 57 +++++++++++++++++++ src/main/resources/schema.yml | 25 +++++++- .../cli/command/AssignOrgToProjectSpec.groovy | 40 +++++++++++++ .../justserve/client/ProjectClientSpec.groovy | 21 +++++++ 5 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/justserve/cli/command/AssignOrgToProject.java create mode 100644 src/test/groovy/org/justserve/cli/command/AssignOrgToProjectSpec.groovy diff --git a/src/main/java/org/justserve/cli/JustServeCommand.java b/src/main/java/org/justserve/cli/JustServeCommand.java index 4bb71a2..28905de 100644 --- a/src/main/java/org/justserve/cli/JustServeCommand.java +++ b/src/main/java/org/justserve/cli/JustServeCommand.java @@ -1,16 +1,13 @@ package org.justserve.cli; import io.micronaut.configuration.picocli.PicocliRunner; -import org.justserve.cli.command.BaseCommand; -import org.justserve.cli.command.GetTempPassword; -import org.justserve.cli.command.MakeOrgAdmin; -import org.justserve.cli.command.UnReassignProjects; +import org.justserve.cli.command.*; import org.justserve.cli.util.JustServeVersionProvider; import picocli.CommandLine.Command; import picocli.CommandLine.ParameterException; import picocli.jansi.graalvm.AnsiConsole; -@Command(subcommands = {GetTempPassword.class, MakeOrgAdmin.class, UnReassignProjects.class}, +@Command(subcommands = {GetTempPassword.class, MakeOrgAdmin.class, UnReassignProjects.class, AssignOrgToProject.class}, mixinStandardHelpOptions = true, name = "justserve", versionProvider = JustServeVersionProvider.class, description = "justserve-cli is a terminal tool to help specialists and admin using JustServe") diff --git a/src/main/java/org/justserve/cli/command/AssignOrgToProject.java b/src/main/java/org/justserve/cli/command/AssignOrgToProject.java new file mode 100644 index 0000000..29f33e8 --- /dev/null +++ b/src/main/java/org/justserve/cli/command/AssignOrgToProject.java @@ -0,0 +1,57 @@ +package org.justserve.cli.command; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import lombok.extern.slf4j.Slf4j; +import org.justserve.client.ProjectClient; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.util.UUID; + +import static org.justserve.cli.util.JustServePrinter.printError; +import static org.justserve.cli.util.JustServePrinter.printNormal; + +@Slf4j +@Command(name = "assignOrgToProject", description = "Assigns an organization to a project", mixinStandardHelpOptions = true) +public class AssignOrgToProject extends BaseCommand implements Runnable { + + @Option(names = {"--project", "-p"}, description = "the project ID", required = true) + private UUID projectId; + + @Option(names = {"--org", "-o"}, description = "the organization ID", required = true) + private UUID orgId; + + @Inject + Provider projectClientProvider; + + @Override + public void run() { + if (isTokenInvalid()) { + return; + } + + ProjectClient client = projectClientProvider.get(); + + try { + log.atTrace().log("Assigning organization {} to project {}", orgId, projectId); + HttpResponse response = client.assignOrganizationToProject(projectId, orgId); + if (response.status() == HttpStatus.OK) { + printNormal("Successfully assigned organization %s to project %s", orgId, projectId); + log.atTrace().log("received api response status: {}", response.status()); + } else { + printError("Failed to assign organization " + orgId + " to project " + projectId + + ". Expected HTTP Status 'OK', but got " + response.status()); + log.atError().log("Failed to assign organization {} to project {}. Expected HTTP Status 'OK', but got {}", + orgId, projectId, response.status()); + } + } catch (HttpClientResponseException e) { + printError("Failed to assign organization %s to project %s. (%s: %s)", + orgId, projectId, e.getStatus().getCode(), e.getMessage()); + log.atError().setCause(e).log("Error response from API: {}", e.getResponse().body()); + } + } +} diff --git a/src/main/resources/schema.yml b/src/main/resources/schema.yml index f23e0b4..d68296f 100644 --- a/src/main/resources/schema.yml +++ b/src/main/resources/schema.yml @@ -150,6 +150,29 @@ paths: application/json: schema: type: null + /api/v1/projects/{id}/organization/{organizationId}/assign: + put: + tags: [ Project ] + description: Assigns an organization to a project. + operationId: assignOrganizationToProject + parameters: + - name: id + in: path + description: ID of the project + required: true + schema: { type: string, format: uuid } + - name: organizationId + in: path + description: ID of the organization to assign + required: true + schema: { type: string, format: uuid } + responses: + '200': + description: OK + content: + application/json: + schema: + type: null /api/v1/users: post: description: Register a new user on JustServe @@ -1231,7 +1254,7 @@ components: description: {} url: {} internalUrl: {} - organizationId: {} + organizationId: { type: string, format: uuid } reviewedBy: {} reviewedOn: {} linked: diff --git a/src/test/groovy/org/justserve/cli/command/AssignOrgToProjectSpec.groovy b/src/test/groovy/org/justserve/cli/command/AssignOrgToProjectSpec.groovy new file mode 100644 index 0000000..9c59f0b --- /dev/null +++ b/src/test/groovy/org/justserve/cli/command/AssignOrgToProjectSpec.groovy @@ -0,0 +1,40 @@ +package org.justserve.cli.command + +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import org.justserve.model.ProjectCard +import spock.lang.Execution + +import static org.spockframework.runtime.model.parallel.ExecutionMode.SAME_THREAD + +@Execution(SAME_THREAD) +@MicronautTest +class AssignOrgToProjectSpec extends BaseCommandSpec { + + def "can assign an organization to a project"() { + given: + def orgSearchResponse = authOrgClient.searchByLocation(createSearchRequestForElkGrove()) + UUID orgId = orgSearchResponse.body().organizations.first().id + ProjectCard project = searchResults.first() + def args = ["assignOrgToProject", "-p", project.getId().toString(), "-o", orgId.toString()] + + when: + def (outputStream, errorStream) = executeCommand(ctx, args as String[]) + + then: + errorStream.matches(blankRegex) + outputStream.contains("Successfully assigned organization ${orgId} to project ${project.getId()}") + } + + def "fails gracefully when project or org does not exist"() { + given: + UUID fakeId = UUID.randomUUID() + def args = ["assignOrgToProject", "-p", fakeId.toString(), "-o", fakeId.toString()] + + when: + def (outputStream, errorStream) = executeCommand(ctx, args as String[]) + + then: + outputStream.matches(blankRegex) + errorStream.contains("Failed to assign organization ${fakeId} to project ${fakeId}. (400: Client 'justserve': Bad Request)") + } +} diff --git a/src/test/groovy/org/justserve/client/ProjectClientSpec.groovy b/src/test/groovy/org/justserve/client/ProjectClientSpec.groovy index f2d55c2..37a9221 100644 --- a/src/test/groovy/org/justserve/client/ProjectClientSpec.groovy +++ b/src/test/groovy/org/justserve/client/ProjectClientSpec.groovy @@ -73,4 +73,25 @@ class ProjectClientSpec extends JustServeSpec { } } + void "can assign an organization to a project"() { + given: + def orgSearchResponse = authOrgClient.searchByLocation(createSearchRequestForElkGrove()) + UUID orgId = orgSearchResponse.body().organizations.first().id + ProjectCard project = searchResults.first() + + when: + def response = projectClient.assignOrganizationToProject(project.getId(), orgId) + + then: + verifyAll { + response.status == OK + } + + and: "validate that the reassignment worked - this is testing the underlying tech, not our codebase" + def updatedProject = projectClient.getProject(project.getId(), "en-US", new GetProjectRequest()).body() + verifyAll { + updatedProject.organization.organizationId == orgId + } + } + } From 9118b2f664d589c9a7fafdfd788d7511343adb69 Mon Sep 17 00:00:00 2001 From: jonathan zollinger Date: Thu, 29 Jan 2026 12:43:06 -0700 Subject: [PATCH 2/2] fix: don't print logs of any level to console --- src/main/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index b1bec03..144bb8f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file