Skip to content

Commit

Permalink
feat(endpoint): add a new "/latest" endpoint
Browse files Browse the repository at this point in the history
Allows to retrieve latest version number for a specific channel

Signed-off-by: Benjamin Gaussorgues <[email protected]>
  • Loading branch information
Altahrim committed Jan 21, 2025
1 parent d0f5850 commit 1dc2d0c
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .htaccess
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Rewrite all requests to "index.php"
# Rewrite all requests to "index.php" except for latest

DirectoryIndex index.php
RewriteEngine On

RewriteRule ^latest(?|$) latest.php [L]
RewriteRule ^ index.php [L]
81 changes: 81 additions & 0 deletions latest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
declare(strict_types = 1);
require_once __DIR__.'/build/utils.php';

// Send headers
header('Content-Type: application/json');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('X-Content-Type-Options: nosniff');
header('X-Robots-Tag: none');
header('Content-Security-Policy: default-src \'none\';');

$channel = $_GET['channel'] ?? 'stable';
$phpVersion = $_GET['php'] ?? '';
if (!preg_match('/^\d{1,2}\.\d{1,2}(?:\.\d{1,2}(?: \w{,20})?)?$|^$/', $phpVersion)){
http_response_code(400);
echo json_encode(['error' => 'Invalid PHP version provided']);
exit;
}

if (!in_array($channel, ['stable', 'beta', 'enterprise'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid channel provided']);
exit;
}

// Filter suitable major versions
$majorVersions = loadJson('major_versions');
try {
$enterpriseMajorVersions = loadJson('enterprise_major_versions');
foreach($majorVersions as $name => $version) {
if (isset($enterpriseMajorVersions[$name])) {
$majorVersions[$name] = array_merge($version, $enterpriseMajorVersions[$name]);
}
}
$majorVersions = array_replace_recursive($majorVersions + $enterpriseMajorVersions);
} catch (\Exception) {
if ($channel === 'enterprise') {
http_response_code(400);
echo json_encode(['error' => 'Invalid channel provided']);
exit;
}
}

if ($phpVersion) {
$majorVersions = array_filter($majorVersions, static function ($release) use ($phpVersion) {
return version_compare($release['minPHP'], $phpVersion, '<=');
});
}

if (empty($majorVersions)) {
http_response_code(400);
echo json_encode(['error' => 'No major version found for your version of PHP']);
exit;
}
$suitableMajors = array_keys($majorVersions);

// Filter suitable releases
$allowedChannels = [$channel];
if ($channel === 'beta') {
$allowedChannels[] = 'stable';
}
$releases = loadJson($channel === 'enterprise' ? 'enterprise_releases' : 'releases');
$releases = array_filter($releases, function ($name) use ($allowedChannels, $suitableMajors) {
return in_array(getStabilityFromName($name), $allowedChannels)
&& preg_match('/^('.implode('|', $suitableMajors).')\./', $name);
}, ARRAY_FILTER_USE_KEY);
uksort($releases, static fn($a, $b) => version_compare($a, $b, '<'));
if (empty($releases)) {
http_response_code(400);
echo json_encode(['error' => 'No version found for your version of PHP']);
exit;
}
// Show latest
$release = key($releases);
$info = current($releases);
echo json_encode([
'version' => $release,
'url' => buildDownloadUrl($release, $info, $majorVersions[explode('.', $release)[0]]),
], JSON_UNESCAPED_SLASHES);

81 changes: 79 additions & 2 deletions tests/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
private $result = '';
/** @var array */
private $resultArray = [];
private ?string $phpVersion = null;

/**
* @Given There is a release with channel :arg1
Expand Down Expand Up @@ -197,6 +198,16 @@ public function theResponseIsNonEmpty() {
throw new \Exception('Response contains not between 6 or 8 array elements.');
}
}
/**
* @Then The JSON response is non-empty
*/
public function theJsonResponseIsNonEmpty() {
if(empty($this->result)) {
throw new \Exception('Response is empty');
}

$this->resultArray = json_decode($this->result, true);
}

/**
* @Then Update to version :arg1 is available
Expand Down Expand Up @@ -260,7 +271,7 @@ public function theResponseIsEmpty() {
}

/**
* @Then Autoupdater is set to :arg1
* @Then Autoupdater is set to :autoupdaterValue
*/
public function autoupdaterIsSetTo($autoupdaterValue) {
$autoupdater = $this->resultArray['autoupdater'];
Expand All @@ -270,12 +281,78 @@ public function autoupdaterIsSetTo($autoupdaterValue) {
}

/**
* @Then EOL is set to :arg1
* @Then EOL is set to :eolValue
*/
public function eolIsSetTo($eolValue) {
$eol = $this->resultArray['eol'];
if($eol !== $eolValue) {
throw new \Exception("Expected eol $eolValue does not equals $eol");
}
}

/**
* @Given I want to know the latest :channel release
*/
public function iWantToKnowTheLatestRelease(string $channel): void
{
$this->channel = $channel;
}

/**
* @Given I use PHP ":phpVersion"
*/
public function iUsePhp($phpVersion): void
{
$this->phpVersion = $phpVersion;
}

/**
* @When I send a request latest.php
*/
public function iSendARequestLatestPhp(): void
{
$ch = curl_init();
$params = [
'channel' => $this->channel,
];
if ($this->phpVersion !== null) {
$params['php'] = $this->phpVersion;
}
$optArray = array(
CURLOPT_URL => 'http://localhost:8888/latest.php?'.http_build_query($params),
CURLOPT_RETURNTRANSFER => true
);
curl_setopt_array($ch, $optArray);
$this->result = curl_exec($ch);
curl_close($ch);
}

/**
* @Then Version :expectedVersion is the latest release
*/
public function versionIsTheLatestRelease($expectedVersion): void
{
if (($this->resultArray['version'] ?? '') === '') {
throw new \Exception("Version number is empty");
}
$foundVersion = $this->resultArray['version'];
if ($foundVersion !== $expectedVersion) {
throw new \Exception("Version number $foundVersion is different from expected version $expectedVersion");
}
}

/**
* @Then I get error ":expectedError"
*/
public function iGetError($expectedError): void
{
if (($this->resultArray['error'] ?? '') === '') {
throw new \Exception("Error message is empty");
}
$foundError = $this->resultArray['error'];
if ($foundError !== $expectedError) {
throw new \Exception("Error message $foundError is different from expected error $expectedError");
}

}
}
52 changes: 52 additions & 0 deletions tests/integration/features/latest.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Feature: Testing the latest endpoint

Scenario: Get latest stable version
Given I want to know the latest stable release
When I send a request latest.php
Then The JSON response is non-empty
And Version "30.0.5" is the latest release
And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-30.0.5.zip"

Scenario: Get latest beta version
Given I want to know the latest beta release
When I send a request latest.php
Then The JSON response is non-empty
And Version "31.0.0 beta 5" is the latest release
And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-31.0.0beta5.zip"

Scenario: Get latest stable version with PHP 8.0
Given I want to know the latest stable release
And I use PHP "8.0"
When I send a request latest.php
Then The JSON response is non-empty
And Version "29.0.11" is the latest release
And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.11.zip"

Scenario: Get latest beta version with PHP 8.0
Given I want to know the latest beta release
And I use PHP "8.0"
When I send a request latest.php
Then The JSON response is non-empty
And Version "29.0.11" is the latest release
And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.11.zip"

Scenario: Get latest version with invalid PHP version
Given I want to know the latest beta release
And I use PHP "test"
When I send a request latest.php
Then The JSON response is non-empty
And I get error "Invalid PHP version provided"

Scenario: Get latest version with too old PHP version
Given I want to know the latest beta release
And I use PHP "5.0"
When I send a request latest.php
Then The JSON response is non-empty
And I get error "No major version found for your version of PHP"

Scenario: Get latest version with invalid channel
Given I want to know the latest whatever release
When I send a request latest.php
Then The JSON response is non-empty
And I get error "Invalid channel provided"

0 comments on commit 1dc2d0c

Please sign in to comment.