Refactor projects fetching commits from Github

This commit is contained in:
Alex Cabal 2024-12-15 14:51:48 -06:00
parent 323b8030b9
commit 3b9ea27391
3 changed files with 110 additions and 58 deletions

View file

@ -1,5 +1,12 @@
<? <?
use function Safe\curl_exec;
use function Safe\curl_getinfo;
use function Safe\curl_init;
use function Safe\curl_setopt;
use function Safe\json_decode;
use function Safe\preg_match; use function Safe\preg_match;
use function Safe\preg_replace;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
/** /**
@ -137,6 +144,18 @@ class Project{
public function Create(): void{ public function Create(): void{
$this->Validate(); $this->Validate();
try{
$this->FetchLatestCommit();
}
catch(Exceptions\AppException){
// Pass; it's OK if this fails during creation.
}
// Don't let the started date be later than the first commit date. This can happen if the producer starts to commit before their project is approved on the mailing list.
if($this->LastCommitTimestamp !== null && $this->LastCommitTimestamp > $this->Started){
$this->Started = $this->LastCommitTimestamp;
}
// Is this ebook already released? // Is this ebook already released?
if(!$this->Ebook->IsPlaceholder()){ if(!$this->Ebook->IsPlaceholder()){
throw new Exceptions\EbookIsNotAPlaceholderException(); throw new Exceptions\EbookIsNotAPlaceholderException();
@ -233,6 +252,65 @@ class Project{
$this->PropertyFromHttp('ReviewerUserId'); $this->PropertyFromHttp('ReviewerUserId');
} }
/**
* @throws Exceptions\AppException If the operation faile.d
*/
public function FetchLatestCommit(?string $apiKey = null): void{
$headers = [
'Accept: application/vnd.github+json',
'X-GitHub-Api-Version: 2022-11-28',
'User-Agent: Standard Ebooks' // Required by GitHub.
];
if($apiKey !== null){
$headers[] = 'Authorization: Bearer ' . $apiKey;
}
// First, we check if the repo has been renamed. If so, update the repo now.
$curl = curl_init($this->VcsUrl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, Enums\HttpMethod::Head->value); // Only perform HTTP HEAD.
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_exec($curl);
/** @var string $finalUrl */
$finalUrl = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
// Were we redirected?
if($finalUrl != $this->VcsUrl){
$this->VcsUrl = $finalUrl;
}
// Now check the actual commits.
$url = preg_replace('|^https://github.com/|iu', 'https://api.github.com/repos/', $this->VcsUrl . '/commits');
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
try{
$response = curl_exec($curl);
/** @var int $httpCode */
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if(!is_string($response)){
throw new Exceptions\AppException('Response from GitHub was not a string: ' . $response);
}
if($httpCode != Enums\HttpCode::Ok->value){
throw new Exception('HTTP code from GitHub was: ' . $httpCode);
}
/** @var array<stdClass> $commits */
$commits = json_decode($response);
if(sizeof($commits) > 0){
$this->LastCommitTimestamp = new DateTimeImmutable($commits[0]->commit->committer->date);
}
}
catch(Exception $ex){
throw new Exceptions\AppException('Error in update-project-commits for URL <' . $url . '>: ' . $ex->getMessage(), 0, $ex);
}
}
// *********** // ***********
// ORM METHODS // ORM METHODS

View file

@ -2,87 +2,55 @@
<? <?
require_once('/standardebooks.org/web/lib/Core.php'); require_once('/standardebooks.org/web/lib/Core.php');
use function Safe\file_get_contents;
/** /**
* Iterate over all `Project`s that are in progress or stalled and get their latest GitHub commit. If the commit is more than 30 days old, mark the `Project` as stalled. * Iterate over all `Project`s that are in progress or stalled and get their latest GitHub commit. If the commit is more than 30 days old, mark the `Project` as stalled.
*/ */
use function Safe\curl_exec;
use function Safe\curl_getinfo;
use function Safe\curl_init;
use function Safe\curl_setopt;
use function Safe\file_get_contents;
use function Safe\json_decode;
use function Safe\preg_replace;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
$apiToken = trim(file_get_contents('/standardebooks.org/config/secrets/se-vcs-bot@api.github.com'));
$headers = [
'Accept: application/vnd.github+json',
'Authorization: Bearer ' . $apiToken,
'X-GitHub-Api-Version: 2022-11-28',
'User-Agent: Standard Ebooks' // Required by GitHub.
];
$projects = array_merge( $projects = array_merge(
Project::GetAllByStatus(Enums\ProjectStatusType::InProgress), Project::GetAllByStatus(Enums\ProjectStatusType::InProgress),
Project::GetAllByStatus(Enums\ProjectStatusType::Stalled) Project::GetAllByStatus(Enums\ProjectStatusType::Stalled)
); );
$apiKey = trim(file_get_contents('/standardebooks.org/config/secrets/se-vcs-bot@api.github.com'));
$oldestAllowedCommitTimestamp = new DateTimeImmutable('30 days ago');
foreach($projects as $project){ foreach($projects as $project){
// First, we check if the repo has been renamed. If so, update the repo now.
$curl = curl_init($project->VcsUrl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, Enums\HttpMethod::Head->value); // Only perform HTTP HEAD.
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_exec($curl);
$finalUrl = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
// Were we redirected?
if($finalUrl != $project->VcsUrl){
$project->VcsUrl = $finalUrl;
$project->Save();
}
// Now check the actual commits.
$url = preg_replace('|^https://github.com/|iu', 'https://api.github.com/repos/', $project->VcsUrl . '/commits');
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
try{ try{
$response = curl_exec($curl); $project->FetchLatestCommit($apiKey);
/** @var int $httpCode */
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if(!is_string($response)){ if(
throw new Exception('Response from GitHub was not a string: ' . $response); $project->Status == Enums\ProjectStatusType::InProgress
} &&
$project->LastCommitTimestamp !== null
if($httpCode != Enums\HttpCode::Ok->value){ &&
throw new Exception('HTTP code from GitHub was: ' . $httpCode); $project->LastCommitTimestamp < $oldestAllowedCommitTimestamp
} ){
// An active `Project` has stalled.
/** @var array<stdClass> $commits */
$commits = json_decode($response);
if(sizeof($commits) > 0){
$project->LastCommitTimestamp = new DateTimeImmutable($commits[0]->commit->committer->date);
}
if($project->LastCommitTimestamp !== null && $project->LastCommitTimestamp < new DateTimeImmutable('30 days ago')){
$project->Status = Enums\ProjectStatusType::Stalled; $project->Status = Enums\ProjectStatusType::Stalled;
} }
else{ elseif(
$project->Status == Enums\ProjectStatusType::Stalled
&&
$project->LastCommitTimestamp !== null
&&
$project->LastCommitTimestamp >= $oldestAllowedCommitTimestamp
){
// Revive previously-stalled `Project`s. // Revive previously-stalled `Project`s.
$project->Status = Enums\ProjectStatusType::InProgress; $project->Status = Enums\ProjectStatusType::InProgress;
} }
$project->Save(); $project->Save();
} }
catch(Exception $ex){ catch(Exceptions\AppException $ex){
Log::WriteErrorLogEntry('Error in update-project-commits for URL <' . $url . '>: ' . $ex->getMessage()); Log::WriteErrorLogEntry($ex->getMessage());
} }
sleep(1); sleep(1);

View file

@ -20,6 +20,7 @@ $includeStatus = $includeStatus ?? true;
<th scope="col">Status</th> <th scope="col">Status</th>
<? } ?> <? } ?>
<th/> <th/>
<th/>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -38,10 +39,10 @@ $includeStatus = $includeStatus ?? true;
<? } ?> <? } ?>
</td> </td>
<td> <td>
<?= $project->LastCommitTimestamp?->format(Enums\DateTimeFormat::ShortDate->value) ?> <?= $project->Started->format(Enums\DateTimeFormat::ShortDate->value) ?>
</td> </td>
<td> <td>
<?= $project->Started->format(Enums\DateTimeFormat::ShortDate->value) ?> <?= $project->LastCommitTimestamp?->format(Enums\DateTimeFormat::ShortDate->value) ?>
</td> </td>
<? if($includeStatus){ ?> <? if($includeStatus){ ?>
<td class="status"> <td class="status">
@ -51,6 +52,11 @@ $includeStatus = $includeStatus ?? true;
<td> <td>
<a href="<?= Formatter::EscapeHtml($project->VcsUrl) ?>">GitHub</a> <a href="<?= Formatter::EscapeHtml($project->VcsUrl) ?>">GitHub</a>
</td> </td>
<td>
<? if($project->DiscussionUrl !== null){ ?>
<a href="<?= Formatter::EscapeHtml($project->DiscussionUrl) ?>">Discussion</a>
<? } ?>
</td>
</tr> </tr>
<? } ?> <? } ?>
</tbody> </tbody>