first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/casts/DatetimeToInt.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class DatetimeToInt
*
* @brief Cast timestamp/int to Carbon datetime
*/
namespace PKP\job\casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class DatetimeToInt implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $attributes
*
* @return \Carbon\Carbon
*/
public function get($model, $key, $value, $attributes)
{
return Carbon::parse($value);
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param string $value
* @param array $attributes
*
* @return int
*/
public function set($model, $key, $value, $attributes)
{
if ($value instanceof Carbon) {
return $value->timestamp;
}
return Carbon::parse($value)->timestamp;
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/exceptions/JobException.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class JobException
*
*/
namespace PKP\job\exceptions;
use Exception;
class JobException extends Exception
{
public const INVALID_PAYLOAD = 'invalid.job.payload';
/**
* Customize the class getMessage()
*/
public function __toString(): string
{
return self::class .
": [{$this->getCode()}] in " .
"{$this->getFile()} ({$this->getLine()}): " .
"{$this->getMessage()}\n";
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/models/FailedJob.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class FailedJob
*
* @brief Laravel Eloquent model for Failed Jobs table
*/
namespace PKP\job\models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use PKP\job\traits\Attributes;
class FailedJob extends Model
{
use Attributes;
/**
* Model's database table
*
* @var string
*/
protected $table = 'failed_jobs';
/**
* Model's primary key
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Model's timestamp fields
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are not mass assignable.
*
* @var string[]|bool
*/
protected $guarded = [];
/**
* Casting attributes to their native types
*
* @var string[]
*/
protected $casts = [
'connection' => 'string',
'payload' => 'array',
'queue' => 'string',
'exception' => 'string',
'failed_at' => 'datetime',
];
/**
* Add a local scope to handle jobs associated in a queue
*/
public function scopeQueuedAt(Builder $query, string $queue): Builder
{
return $query->where('queue', $queue);
}
/**
* Return the core exception message without the full exception trace
*/
public function exceptionMessage(): string
{
if (isValidJson($this->exception)) {
$exception = json_decode($this->exception);
return $exception->message . ' in ' . $exception->file . ' at ' . $exception->line;
}
return preg_replace('/\s+/', ' ', trim(explode('Stack trace', $this->exception)[0]));
}
}
+240
View File
@@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/models/Job.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Job
*
* @brief Laravel Eloquent model for Jobs table
*/
namespace PKP\job\models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\InteractsWithTime;
use PKP\config\Config;
use PKP\job\casts\DatetimeToInt;
use PKP\job\traits\Attributes;
class Job extends Model
{
use Attributes;
use InteractsWithTime;
protected const DEFAULT_MAX_ATTEMPTS = 3;
public const TESTING_QUEUE = 'queuedTestJob';
/**
* Default queue
*
* @var string
*/
protected $defaultQueue;
/**
* Max Attempts
*
* @var int
*/
protected $maxAttempts;
/**
* Model's database table
*
* @var string
*/
protected $table = 'jobs';
/**
* Model's primary key
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Model's timestamp fields
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are not mass assignable.
*
* @var string[]|bool
*/
protected $guarded = [];
/**
* Casting attributes to their native types
*
* @var string[]
*/
protected $casts = [
'queue' => 'string',
'payload' => 'array',
'attempts' => 'int',
'reserved_at' => DatetimeToInt::class,
'available_at' => DatetimeToInt::class,
'created_at' => DatetimeToInt::class,
];
/**
* Instantiate the Job model
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->setDefaultQueue(Config::getVar('queues', 'default_queue', 'queue'));
$this->setMaxAttempts(self::DEFAULT_MAX_ATTEMPTS);
}
/**
* Set the default queue
*/
public function setDefaultQueue(?string $value): self
{
$this->defaultQueue = $value;
return $this;
}
/**
* Get Queue name, in case of nullable value, will be the default one
*/
public function getQueue(?string $queue): string
{
return $queue ?: $this->defaultQueue;
}
/**
* Set the Job's max attempts
*/
public function setMaxAttempts(int $maxAttempts): self
{
$this->maxAttempts = $maxAttempts;
return $this;
}
/**
* Get the Job's max attempts
*/
public function getMaxAttempts(): int
{
return $this->maxAttempts;
}
/**
* Add a local scope for not exceeded attempts
*/
public function scopeNotExceededAttempts(Builder $query): Builder
{
return $query->where('attempts', '<', $this->getMaxAttempts());
}
/**
* Add a local scope to handle jobs associated in a queue
*/
public function scopeQueuedAt(
Builder $query,
?string $queue = null
): Builder {
return $query->where('queue', $this->getQueue($queue));
}
/**
* Add a local scope to get jobs with queue must defined
*/
public function scopeNonEmptyQueue(Builder $query): Builder
{
return $query->whereNotNull('queue');
}
/**
* Add a local scope to filter jobs by not given queue
*/
public function scopeNotQueue(Builder $query, string $queue): Builder
{
return $query->where('queue', '!=', $queue);
}
/**
* Add a local scope to filter jobs by given queue
*/
public function scopeOnQueue(Builder $query, string $queue): Builder
{
return $query->where('queue', '=', $queue);
}
/**
* Add a local scope to filter jobs by non reserved
*/
public function scopeNonReserved(Builder $query): Builder
{
return $query->whereNull('reserved_at');
}
/**
* Retrieve available jobs
*/
public function scopeIsAvailable(Builder $query): Builder
{
return $query->whereNull('reserved_at')
->where('available_at', '<=', $this->currentTime());
}
/**
* Get queue's size
*/
public function size(?string $queue = null): int
{
return $this->queuedAt($this->getQueue($queue))
->count();
}
/**
* Increment the Job attempts
*/
public function incrementAttempts(): void
{
$this->increment('attempts');
}
/**
* Mark a job as reserved to avoid being run by another process
*/
public function markJobAsReserved(): void
{
$this->update([
'reserved_at' => $this->currentTime(),
'attempts' => $this->attempts++,
]);
}
/**
* Set the reserved_at attribute
*/
public function setReservedAtAttribute(int $value): self
{
if (!$value) {
$this->attributes['reserved_at'] = null;
return $this;
}
$this->attributes['reserved_at'] = $value;
return $this;
}
}
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/repositories/BaseRepository.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class BaseRepository
*
* @brief Abstract class BaseRepository
*/
namespace PKP\job\repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\LengthAwarePaginator;
abstract class BaseRepository
{
protected Model $model;
protected int $perPage = 50;
protected ?string $outputFormat;
public const OUTPUT_CLI = 'cli';
public const OUTPUT_HTTP = 'http';
public function newQuery(): Builder
{
return $this->model->newQuery();
}
public function all(array $columns = ['*']): Collection
{
return $this->model->all($columns);
}
public function get(int $modelId): ?Model
{
return $this->model->find($modelId);
}
public function add(array $attributes = []): ?Model
{
return $this->model->create($attributes);
}
public function edit(int $modelId, array $data): bool
{
return $this->model->find($modelId)->update($data);
}
public function delete(int $modelId): bool
{
return $this->model->find($modelId)->delete();
}
public function total(): int
{
return $this->model->count();
}
public function setOutputFormat(string $format): self
{
$this->outputFormat = $format;
return $this;
}
public function setPage(int $page): self
{
LengthAwarePaginator::currentPageResolver(fn () => $page);
return $this;
}
public function perPage(int $perPage): self
{
$this->perPage = $perPage;
return $this;
}
public function deleteJobs(string $queue = null, array $ids = []): int
{
$query = $this->model->newQuery();
if ($queue) {
$query = $query->queuedAt($queue);
}
if (!empty($ids)) {
$query = $query->whereIn('id', $ids);
}
return $query->delete();
}
/**
* Show jobs
*/
abstract public function showJobs(): LengthAwarePaginator;
}
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/repositories/Job.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class FailedJob
*
* @brief Job Repository
*/
namespace PKP\job\repositories;
use Carbon\Carbon;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use PKP\facades\Repo;
use PKP\job\models\FailedJob as PKPFailedJobModel;
use PKP\job\resources\CLIFailedJobResource;
use PKP\job\resources\HttpFailedJobResource;
class FailedJob extends BaseRepository
{
public function __construct(PKPFailedJobModel $model)
{
$this->model = $model;
}
public function showJobs(): LengthAwarePaginator
{
$currentPage = LengthAwarePaginator::resolveCurrentPage();
$sanitizedPage = $currentPage - 1;
$offsetRows = $this->perPage * $sanitizedPage;
$total = $this->model->count();
$data = $this->model
->skip($offsetRows)
->take($this->perPage)
->get();
return new LengthAwarePaginator(
$this->getOutput($data),
$total,
$this->perPage
);
}
public function getRedispatchableJobsInQueue(string $queue = null, array $columns = ['*']): collection
{
$failedJobs = $this->newQuery()->select($columns);
if ($queue) {
$failedJobs = $failedJobs->queuedAt($queue);
}
return $failedJobs->where(
fn ($query) => $query
->whereNotNull('payload')
->whereRaw("payload <> ''")
)->get();
}
public function redispatchToQueue(string $queue = null, array $failedIds = []): int
{
$failedJobs = $this->newQuery();
if ($queue) {
$failedJobs = $failedJobs->queuedAt($queue);
}
if (!empty($failedIds)) {
$failedJobs = $failedJobs->whereIn('id', $failedIds);
}
$failedJobs = $failedJobs->get();
DB::beginTransaction();
$failedJobs->each(fn ($failedJob) => Repo::job()->add([
'queue' => $failedJob->queue,
'payload' => $failedJob->payload,
'attempts' => 0,
'available_at' => Carbon::now()->timestamp,
'created_at' => Carbon::now()->timestamp,
]));
DB::commit();
return $failedJobs->toQuery()->delete();
}
protected function getOutput(Collection $data)
{
if ($this->outputFormat === self::OUTPUT_CLI) {
return CLIFailedJobResource::collection($data);
}
return HttpFailedJobResource::collection($data);
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/repositories/Job.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Job
*
* @brief Job Repository
*/
namespace PKP\job\repositories;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use PKP\job\models\Job as PKPJobModel;
use PKP\job\resources\CLIJobResource;
use PKP\job\resources\HttpJobResource;
class Job extends BaseRepository
{
public function __construct(PKPJobModel $model)
{
$this->model = $model;
}
public function total(): int
{
return $this->model
->nonEmptyQueue()
->nonReserved()
->count();
}
public function showJobs(): LengthAwarePaginator
{
$currentPage = LengthAwarePaginator::resolveCurrentPage();
$sanitizedPage = $currentPage - 1;
$offsetRows = $this->perPage * $sanitizedPage;
$query = $this->model
->nonEmptyQueue()
->nonReserved();
$total = $query->count();
$data = $query
->skip($offsetRows)
->take($this->perPage)
->get();
return new LengthAwarePaginator(
$this->getOutput($data),
$total,
$this->perPage
);
}
protected function getOutput(Collection $data)
{
if ($this->outputFormat == self::OUTPUT_CLI) {
return CLIJobResource::collection($data);
}
return HttpJobResource::collection($data);
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/resources/CLIFailedJobResource.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class CLIFailedJobResource
*
* @brief Mapping class for CLI output values
*/
namespace PKP\job\resources;
use Illuminate\Http\Resources\Json\JsonResource;
use PKP\job\traits\JobResource;
class CLIFailedJobResource extends JsonResource
{
use JobResource;
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return [
'id' => $this->getResource()->id,
'queue' => $this->getResource()->queue,
'displayName' => $this->getJobName(),
'connection' => $this->getResource()->connection,
'failed_at' => $this->getFailedAt(),
'exception' => chunk_split($this->getResource()->exceptionMessage(), 50),
];
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/resources/CLIJobResource.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class CLIJobResource
*
* @brief Mapping class for CLI output values
*/
namespace PKP\job\resources;
use Illuminate\Http\Resources\Json\JsonResource;
use PKP\job\traits\JobResource;
class CLIJobResource extends JsonResource
{
use JobResource;
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return [
'id' => $this->getResource()->id,
'queue' => $this->getResource()->queue,
'displayName' => $this->getJobName(),
'attempts' => $this->getResource()->attempts,
'reserved_at' => $this->getReservedAt(),
'available_at' => $this->getAvailableAt(),
'created_at' => $this->getCreatedAt(),
];
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/resources/HttpFailedJobResource.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class HttpFailedJobResource
*
* @brief Mapping class for HTTP Output values
*/
namespace PKP\job\resources;
use APP\core\Application;
use Illuminate\Http\Resources\Json\JsonResource;
use PKP\job\traits\JobResource;
class HttpFailedJobResource extends JsonResource
{
use JobResource;
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return [
'id' => $this->getResource()->id,
'displayName' => $this->getJobName(),
'queue' => $this->getResource()->queue,
'connection' => $this->getResource()->connection,
'failed_at' => $this->getFailedAt(),
'payload' => $this->getResource()->payload,
'exception' => $this->getResource()->exception,
'_hrefs' => [
'_details' => $request->getDispatcher()->url($request, Application::ROUTE_PAGE, 'index', 'admin', 'failedJobDetails', $this->getResource()->id),
'_redispatch' => $request->getDispatcher()->url($request, Application::ROUTE_API, 'index', 'jobs/redispatch/' . $this->getResource()->id),
'_delete' => $request->getDispatcher()->url($request, Application::ROUTE_API, 'index', 'jobs/failed/delete/' . $this->getResource()->id),
],
];
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/resources/HttpJobResource.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class HttpJobResource
*
* @brief Mapping class for HTTP Output values
*/
namespace PKP\job\resources;
use Illuminate\Http\Resources\Json\JsonResource;
use PKP\job\traits\JobResource;
class HttpJobResource extends JsonResource
{
use JobResource;
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return [
'id' => $this->getResource()->id,
'queue' => $this->getResource()->queue,
'displayName' => $this->getJobName(),
'attempts' => $this->getResource()->attempts,
'created_at' => __('admin.jobs.createdAt', ['createdAt' => $this->getCreatedAt()]),
];
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/traits/Attributes.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Attributes
*
* @brief Attributes trait for Jobs model
*/
namespace PKP\job\traits;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Date;
/**
* Those attributes become from payload array
*/
trait Attributes
{
/**
* Return the job's display name value
*
*/
public function getDisplayNameAttribute(): ?string
{
if (!$this->payload['displayName']) {
return null;
}
return $this->payload['displayName'];
}
/**
* Return the job's max tries value
*
*/
public function getMaxTriesAttribute(): ?string
{
if (!$this->payload['maxTries']) {
return null;
}
return $this->payload['maxTries'];
}
/**
* Return the job's backoff value
*
*/
public function getBackoffAttribute(): ?string
{
if (!$this->payload['backoff']) {
return null;
}
return $this->payload['backoff'];
}
/**
* Return the job's timeout value
*
*/
public function getTimeoutAttribute(): ?string
{
if (!$this->payload['timeout']) {
return null;
}
return $this->payload['timeout'];
}
/**
* Return the job's timeout at value
*
*/
public function getTimeoutAtAttribute(): ?string
{
if (!$this->payload['timeout_at']) {
return null;
}
$obj = new Carbon($this->payload['timeout_at']);
return Date::instance($obj);
}
/**
* Return the job's command name value
*
*/
public function getCommandNameAttribute(): ?string
{
if (!$this->payload['data']['commandName']) {
return null;
}
return $this->payload['data']['commandName'];
}
/**
* Return the job's command value
*
*/
public function getCommandAttribute(): array
{
if (!$this->payload['data']['command']) {
return [];
}
return (array) unserialize($this->payload['data']['command']);
}
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* @file classes/job/traits/JobResource.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class JobResource
*
* @brief JobResource trait
*/
namespace PKP\job\traits;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use PKP\core\PKPRequest;
trait JobResource
{
protected string $dateFormat = 'Y-m-d G:i:s T Z';
public static function toResourceArray(Model $modelInstance): array
{
return (new static($modelInstance))->toArray(app()->get(PKPRequest::class));
}
public function getResource(): mixed
{
return $this->resource;
}
public function getCreatedAt(): string
{
if (!isset($this->getResource()->created_at)) {
return '-';
}
return $this->formatDate($this->getResource()->created_at);
}
public function getReservedAt(): string
{
if (!isset($this->getResource()->reserved_at)) {
return '-';
}
return $this->formatDate($this->getResource()->reserved_at);
}
public function getAvailableAt(): string
{
if (!isset($this->getResource()->available_at)) {
return '-';
}
return $this->formatDate($this->getResource()->available_at);
}
public function getFailedAt(): string
{
if (!isset($this->getResource()->failed_at)) {
return '-';
}
return $this->formatDate($this->getResource()->failed_at);
}
public function getJobName(): ?string
{
if (!isset($this->getResource()->payload)) {
return '-';
}
return $this->getResource()->payload['displayName'] ?? '-';
}
protected function formatDate(Carbon $date): string
{
return $date->format($this->dateFormat);
}
}