Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API Project Version Get / Update / Delete APIs + Tests #1883

Merged
merged 15 commits into from Apr 28, 2023
Merged
112 changes: 109 additions & 3 deletions api/rest/restcore/projects_rest.php
Expand Up @@ -42,8 +42,16 @@
$g_app->delete( '/{id}/', 'rest_project_delete' );

# Project versions
$g_app->get( '/{id}/versions', 'rest_project_version_get' );
$g_app->get( '/{id}/versions/', 'rest_project_version_get' );
$g_app->get( '/{id}/versions/{version_id}', 'rest_project_version_get' );
$g_app->get( '/{id}/versions/{version_id}/', 'rest_project_version_get' );
$g_app->post( '/{id}/versions', 'rest_project_version_add' );
$g_app->post( '/{id}/versions/', 'rest_project_version_add' );
$g_app->patch( '/{id}/versions/{version_id}', 'rest_project_version_update' );
$g_app->patch( '/{id}/versions/{version_id}/', 'rest_project_version_update' );
$g_app->delete( '/{id}/versions/{version_id}', 'rest_project_version_delete' );
$g_app->delete( '/{id}/versions/{version_id}/', 'rest_project_version_delete' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't /{id}/versions be defined as a group to avoid duplication?

Also, what about making use of optional parameters so handle the route variants for a given method ? Something like

$g_app->get( ''/{id}/versions[/{version_id}][/]', 'rest_project_version_get' );

(I have not actually tested this)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll consider this as a follow up later.


# Project hierarchy (subprojects)
$g_app->post( '/{id}/subprojects', 'rest_project_hierarchy_add' );
Expand Down Expand Up @@ -179,6 +187,38 @@ function rest_projects_get( \Slim\Http\Request $p_request, \Slim\Http\Response $
return $p_response->withStatus( HTTP_STATUS_SUCCESS )->withJson( $t_result );
}

/**
* A method to get project version(s).
*
* @param \Slim\Http\Request $p_request The request.
* @param \Slim\Http\Response $p_response The response.
* @param array $p_args Arguments
* @return \Slim\Http\Response The augmented response.
*/
function rest_project_version_get( \Slim\Http\Request $p_request, \Slim\Http\Response $p_response, array $p_args ) {
$t_project_id = isset( $p_args['id'] ) ? $p_args['id'] : $p_request->getParam( 'id' );
if( is_blank( $t_project_id ) ) {
$t_message = "Project id is missing.";
return $p_response->withStatus( HTTP_STATUS_BAD_REQUEST, $t_message );
}

$t_version_id = isset( $p_args['version_id'] ) ? $p_args['version_id'] : $p_request->getParam( 'version_id' );

$t_data = array(
'query' => array(
'project_id' => $t_project_id,
'version_id' => $t_version_id
)
);

$t_command = new VersionGetCommand( $t_data );
$t_result = $t_command->execute();

return $p_response->
withStatus( HTTP_STATUS_SUCCESS, "OK" )->
withJson( $t_result );
}

/**
* A method to add a project version.
*
Expand All @@ -198,16 +238,82 @@ function rest_project_version_add( \Slim\Http\Request $p_request, \Slim\Http\Res

$t_data = array(
'query' => array(
'project_id' => $t_project_id,
'project_id' => $t_project_id
),
'payload' => $t_version_to_add
);

$t_command = new VersionAddCommand( $t_data );
$t_result = $t_command->execute();
$t_version_id = (int)$t_result['id'];
$t_version_id = (int)$t_result['version']['id'];

return $p_response->
withStatus( HTTP_STATUS_CREATED, "Version created with id $t_version_id" )->
withJson( $t_result );
}

/**
* A method to update a project version.
*
* @param \Slim\Http\Request $p_request The request.
* @param \Slim\Http\Response $p_response The response.
* @param array $p_args Arguments
* @return \Slim\Http\Response The augmented response.
*/
function rest_project_version_update( \Slim\Http\Request $p_request, \Slim\Http\Response $p_response, array $p_args ) {
$t_project_id = isset( $p_args['id'] ) ? $p_args['id'] : $p_request->getParam( 'id' );
if( is_blank( $t_project_id ) ) {
$t_message = "Project id is missing.";
return $p_response->withStatus( HTTP_STATUS_BAD_REQUEST, $t_message );
}

$t_version_id = isset( $p_args['version_id'] ) ? $p_args['version_id'] : $p_request->getParam( 'version_id' );
$t_version_to_update = $p_request->getParsedBody();

$t_data = array(
'query' => array(
'project_id' => $t_project_id,
'version_id' => $t_version_id
),
'payload' => $t_version_to_update
);

$t_command = new VersionUpdateCommand( $t_data );
$t_result = $t_command->execute();

return $p_response
->withStatus( HTTP_STATUS_SUCCESS, "Version updated" )
->withJson( $t_result );
}

return $p_response->withStatus( HTTP_STATUS_NO_CONTENT, "Version created with id $t_version_id" );
/**
* A method to delete a project version.
*
* @param \Slim\Http\Request $p_request The request.
* @param \Slim\Http\Response $p_response The response.
* @param array $p_args Arguments
* @return \Slim\Http\Response The augmented response.
*/
function rest_project_version_delete( \Slim\Http\Request $p_request, \Slim\Http\Response $p_response, array $p_args ) {
$t_project_id = isset( $p_args['id'] ) ? $p_args['id'] : $p_request->getParam( 'id' );
if( is_blank( $t_project_id ) ) {
$t_message = "Project id is missing.";
return $p_response->withStatus( HTTP_STATUS_BAD_REQUEST, $t_message );
}

$t_version_id = isset( $p_args['version_id'] ) ? $p_args['version_id'] : $p_request->getParam( 'version_id' );

$t_data = array(
'query' => array(
'project_id' => $t_project_id,
'version_id' => $t_version_id,
)
);

$t_command = new VersionDeleteCommand( $t_data );
$t_command->execute();

return $p_response->withStatus( HTTP_STATUS_NO_CONTENT, "Version deleted" );
}

/**
Expand Down
68 changes: 27 additions & 41 deletions api/soap/mc_project_api.php
Expand Up @@ -466,14 +466,14 @@ function mc_project_version_add( $p_username, $p_password, stdClass $p_version )
'description' => $p_version['description'],
'released' => $p_version['released'],
'obsolete' => isset( $p_version['obsolete'] ) ? $p_version['obsolete'] : false,
'timestamp' => $p_version['date_order'],
'timestamp' => $p_version['date_order']
)
);

$t_command = new VersionAddCommand( $t_data );
$t_result = $t_command->execute();

return $t_result['id'];
return $t_result['version']['id'];
}

/**
Expand All @@ -486,8 +486,6 @@ function mc_project_version_add( $p_username, $p_password, stdClass $p_version )
* @return boolean returns true or false depending on the success of the update action
*/
function mc_project_version_update( $p_username, $p_password, $p_version_id, stdClass $p_version ) {
global $g_project_override;

$t_user_id = mci_check_login( $p_username, $p_password );

if( $t_user_id === false ) {
Expand All @@ -505,11 +503,10 @@ function mc_project_version_update( $p_username, $p_password, $p_version_id, std
$p_version = ApiObjectFactory::objectToArray( $p_version );

$t_project_id = $p_version['project_id'];
$g_project_override = $t_project_id;
$t_name = $p_version['name'];
$t_released = $p_version['released'];
$t_description = $p_version['description'];
$t_date_order = isset( $p_version['date_order'] ) ? strtotime( $p_version['date_order'] ) : null;
$t_date_order = is_blank( $p_version['date_order'] ) ? null : $p_version['date_order'];
$t_obsolete = isset( $p_version['obsolete'] ) ? $p_version['obsolete'] : false;

if( is_blank( $t_project_id ) ) {
Expand All @@ -524,36 +521,23 @@ function mc_project_version_update( $p_username, $p_password, $p_version_id, std
return mci_fault_access_denied( $t_user_id );
}

if( !mci_has_access( config_get( 'manage_project_threshold' ), $t_user_id, $t_project_id ) ) {
return mci_fault_access_denied( $t_user_id );
}

if( is_blank( $t_name ) ) {
return ApiObjectFactory::faultBadRequest( 'Mandatory field "name" was missing' );
}

# check for duplicates
$t_old_version_name = version_get_field( $p_version_id, 'version' );
if( ( strtolower( $t_old_version_name ) != strtolower( $t_name ) ) && !version_is_unique( $t_name, $t_project_id ) ) {
return ApiObjectFactory::faultConflict( 'Version exists for project' );
}

if( $t_released === false ) {
$t_released = VERSION_FUTURE;
} else {
$t_released = VERSION_RELEASED;
}
$t_data = array(
'query' => array(
'project_id' => $t_project_id,
'version_id' => $p_version_id
),
'payload' => array(
'name' => $t_name,
'description' => $t_description,
'released' => $t_released,
'obsolete' => $t_obsolete,
'timestamp' => $t_date_order
)
);

$t_version_data = new VersionData();
$t_version_data->id = $p_version_id;
$t_version_data->project_id = $t_project_id;
$t_version_data->version = $t_name;
$t_version_data->description = $t_description;
$t_version_data->released = $t_released;
$t_version_data->date_order = $t_date_order;
$t_version_data->obsolete = $t_obsolete;
$t_command = new VersionUpdateCommand( $t_data );
$t_command->execute();

version_update( $t_version_data );
return true;
}

Expand All @@ -566,8 +550,6 @@ function mc_project_version_update( $p_username, $p_password, $p_version_id, std
* @return boolean returns true or false depending on the success of the delete action
*/
function mc_project_version_delete( $p_username, $p_password, $p_version_id ) {
global $g_project_override;

$t_user_id = mci_check_login( $p_username, $p_password );

if( $t_user_id === false ) {
Expand All @@ -583,17 +565,21 @@ function mc_project_version_delete( $p_username, $p_password, $p_version_id ) {
}

$t_project_id = version_get_field( $p_version_id, 'project_id' );
$g_project_override = $t_project_id;

if( !mci_has_readwrite_access( $t_user_id, $t_project_id ) ) {
return mci_fault_access_denied( $t_user_id );
}

if( !mci_has_access( config_get( 'manage_project_threshold' ), $t_user_id, $t_project_id ) ) {
return mci_fault_access_denied( $t_user_id );
}
$t_data = array(
'query' => array(
'project_id' => $t_project_id,
'version_id' => $p_version_id
)
);

$t_command = new VersionDeleteCommand( $t_data );
$t_command->execute();

version_remove( $p_version_id );
return true;
}

Expand Down
52 changes: 36 additions & 16 deletions core/commands/VersionAddCommand.php
Expand Up @@ -24,6 +24,13 @@
* A command that adds a project version.
*/
class VersionAddCommand extends Command {
/**
* The project id
*
* @var integer
*/
protected $project_id;

/**
* @var int|null $timestamp
*/
Expand Down Expand Up @@ -51,15 +58,26 @@ function __construct( array $p_data ) {
* @throws ClientException
*/
function validate() {
$t_project_id = helper_parse_id( $this->query( 'project_id' ), 'project_id' );
$this->project_id = helper_parse_id( $this->query( 'project_id' ), 'project_id' );

if( !project_exists( $this->project_id ) ) {
throw new ClientException(
"Project $this->project_id not found",
ERROR_PROJECT_NOT_FOUND,
array( $this->project_id ) );
}

if( !access_has_project_level( config_get( 'manage_project_threshold' ), $t_project_id ) ) {
if( !access_has_project_level( config_get( 'manage_project_threshold' ), $this->project_id ) ) {
throw new ClientException( 'Access denied to add versions', ERROR_ACCESS_DENIED );
}

$t_name = $this->payload( 'name' );
if( is_blank( $t_name ) ) {
throw new ClientException( 'Invalid version name', ERROR_EMPTY_FIELD, array( 'name' ) );
$t_name = $this->payload( 'name', '' );
$t_name = trim( $t_name );
if( !version_is_valid_name( $t_name ) ) {
throw new ClientException(
'Invalid version name',
ERROR_INVALID_FIELD_VALUE,
array( 'name' ) );
}

$t_timestamp = $this->payload( 'timestamp', '' );
Expand All @@ -69,31 +87,33 @@ function validate() {
/**
* Process the command.
*
* @returns array Command response
* @return array Command response
*/
protected function process() {
$t_project_id = helper_parse_id( $this->query( 'project_id' ), 'project_id' );
if( $t_project_id != helper_get_current_project() ) {
# in case the current project is not the same project of the bug we are
# viewing, override the current project. This to avoid problems with
# categories and handlers lists etc.
global $g_project_override;
$g_project_override = $t_project_id;
}
global $g_project_override;

$t_prev_project_id = $g_project_override;
$g_project_override = $this->project_id;

$t_name = trim( $this->payload( 'name' ) );
$t_description = trim( $this->payload( 'description', '' ) );
$t_released = $this->payload( 'released', false );
$t_obsolete = $this->payload( 'obsolete', false );

$t_version_id = version_add(
$t_project_id,
$this->project_id,
$t_name,
$t_released ? VERSION_RELEASED : VERSION_FUTURE,
$t_description,
$this->timestamp,
$t_obsolete );

return array( 'id' => $t_version_id );
version_cache_clear_row( $t_version_id );
$t_version = version_get( $t_version_id );
$t_result = array( 'version' => VersionGetCommand::VersionToArray( $t_version ) );

$g_project_override = $t_prev_project_id;

return $t_result;
}
}