Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/Enums/ReviewSubmissionActionType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace App\Enums;

enum ReviewSubmissionActionType: string {
case SUBMITTED = 'submitted';
case REVIEW_STARTED = 'review_started';
case APPROVED = 'approved';
case REJECTED = 'rejected';
case CANCELLED = 'cancelled';
}
10 changes: 10 additions & 0 deletions app/Enums/UserRole.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace App\Enums;

enum UserRole: string {
case WIKI_MANAGER = 'wiki_manager';
case REVIEW_COMMITTEE_ADMIN = 'review_committee_admin';
}
95 changes: 95 additions & 0 deletions app/ReviewSubmission.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace App;

use Carbon\CarbonImmutable;
use Database\Factories\ReviewSubmissionFactory;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
* @property int $id
* @property int $wiki_id
* @property string|null $additional_information
* @property CarbonImmutable|null $created_at
* @property CarbonImmutable|null $updated_at
* @property-read Collection<int, ReviewSubmissionAction> $actions
* @property-read int|null $actions_count
* @property-read ReviewSubmissionAction|null $latestAction
* @property-read Wiki|null $wiki
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission whereAdditionalInformation($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmission whereWikiId($value)
* @method static \Database\Factories\ReviewSubmissionFactory factory($count = null, $state = [])
*
* @mixin \Eloquent
*/
class ReviewSubmission extends Model {
/** @use HasFactory<ReviewSubmissionFactory> */
use HasFactory;

/**
* The current "state" of a review submission (`$submission->latestAction->type`) is
* frequently required. Always eager loading the latestAction relationship avoids additional
* queries and protects against N+1 query issues when processing multiple submissions.
*
* Use one of the `->without*()` methods to prevent these from being eager loaded.
*
* @var list<string>
*/
protected $with = [
'latestAction',
];

protected function casts(): array {
return [
// cast to `CarbonImmutable` until we default to using `CarbonImmutable` globally in T430656
'created_at' => 'immutable_datetime',
'updated_at' => 'immutable_datetime',
];
}

/**
* Get the Wiki this review submission is for.
*
* Access the related actions through the `wiki` property, or use
* the relationship directly when further querying is required.
*/
public function wiki(): BelongsTo {
return $this->belongsTo(Wiki::class);
}

/**
* Get all actions for this review submission.
*
* Access the related actions through the `actions` property, or use the
* relationship directly when further querying is required.
*/
public function actions(): HasMany {
return $this->hasMany(ReviewSubmissionAction::class);
}

/**
* Get the most recent action for this review submission.
*
* The type of the latest action represents the current "state" of the submission.
*
* Access the action through the `latestAction` property, or use the
* relationship directly when further querying is required.
*/
public function latestAction(): HasOne {
return $this->hasOne(ReviewSubmissionAction::class)->latestOfMany();
}
}
77 changes: 77 additions & 0 deletions app/ReviewSubmissionAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace App;

use App\Enums\ReviewSubmissionActionType;
use App\Enums\UserRole;
use Database\Factories\ReviewSubmissionActionFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;

/**
* @property int $id
* @property int $review_submission_id
* @property int $actor_user_id
* @property string $actor_user_role
* @property ReviewSubmissionActionType $type
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read ReviewSubmission $reviewSubmission
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereActorRole($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereReviewSubmissionId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereUserId($value)
* @method static \Database\Factories\ReviewSubmissionActionFactory factory($count = null, $state = [])
*
* @property-read User $actor
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereActorUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ReviewSubmissionAction whereActorUserRole($value)
*
* @mixin \Eloquent
*/
class ReviewSubmissionAction extends Model {
/** @use HasFactory<ReviewSubmissionActionFactory> */
use HasFactory;

protected function casts(): array {
return [
'actor_user_role' => UserRole::class,
'type' => ReviewSubmissionActionType::class,
// cast to `CarbonImmutable` until we default to using `CarbonImmutable` globally in T430656
'created_at' => 'immutable_datetime',
'updated_at' => 'immutable_datetime',
];
}

/**
* Get the actor that performed this action.
*
* Access the related actions through the `actor` property, or use
* the relationship directly when further querying is required.
*/
public function actor(): BelongsTo {
return $this->belongsTo(User::class, 'actor_user_id');
}

/**
* Get the review submission to which this action belongs.
*
* Access the related actions through the `reviewSubmission` property,
* or use the relationship directly when further querying is required.
*/
public function reviewSubmission(): BelongsTo {
return $this->belongsTo(ReviewSubmission::class);
}
}
52 changes: 37 additions & 15 deletions app/Wiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,45 @@
* @property Carbon|null $deleted_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read Collection|WikiSetting[] $settings
* @property string|null $description
* @property int $is_featured
* @property string|null $wiki_deletion_reason
* @property-read mixed $domain_decoded
* @property-read Collection<int, ReviewSubmission> $reviewSubmissions
* @property-read int|null $review_submissions_count
* @property-read Collection<int, WikiSetting> $settings
* @property-read int|null $settings_count
* @property-read WikiDb|null $wikiDb
* @property-read WikiDb|null $wikiDbVersion
* @property-read Collection|User[] $wikiManagers
* @property-read Collection<int, WikiEntityImport> $wikiEntityImports
* @property-read int|null $wiki_entity_imports_count
* @property-read WikiProfile|null $wikiLatestProfile
* @property-read WikiLifecycleEvents|null $wikiLifecycleEvents
* @property-read Collection<int, WikiManager> $wikiManagers
* @property-read int|null $wiki_managers_count
* @property-read Collection<int, User> $wikiManagersWithEmail
* @property-read int|null $wiki_managers_with_email_count
* @property-read Collection<int, WikiNotificationSentRecord> $wikiNotificationSentRecords
* @property-read int|null $wiki_notification_sent_records_count
* @property-read QueryserviceNamespace|null $wikiQueryserviceNamespace
* @property-read WikiSiteStats|null $wikiSiteStats
*
* @method static \Database\Factories\WikiFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki newQuery()
* @method static \Illuminate\Database\Query\Builder|\App\Wiki onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki query()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki whereDomain($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki whereSitename($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Wiki whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\App\Wiki withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Wiki withoutTrashed()
* @method static \Database\Factories\WikiFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereDescription($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereDomain($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereIsFeatured($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereSitename($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki whereWikiDeletionReason($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Wiki withoutTrashed()
*
* @mixin \Eloquent
*/
Expand Down Expand Up @@ -70,6 +88,10 @@ class Wiki extends Model {
'domain_decoded',
];

public function reviewSubmissions(): HasMany {
return $this->hasMany(ReviewSubmission::class);
}

public function wikiDbVersion() {
return $this->hasOne(WikiDb::class)->select(['id', 'wiki_id', 'version']);
}
Expand Down
39 changes: 39 additions & 0 deletions database/factories/ReviewSubmissionActionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Enums\ReviewSubmissionActionType;
use App\Enums\UserRole;
use App\ReviewSubmission;
use App\ReviewSubmissionAction;
use App\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends Factory<ReviewSubmissionAction>
*/
class ReviewSubmissionActionFactory extends Factory {
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array {
return [
'review_submission_id' => ReviewSubmission::factory(),
// TODO: this should be the same User as `ReviewSubmission::actions->first()->actor`
// and one of the `ReviewSubmission::wiki->wikiManagersWithEmail`.
// Can likely be achieved with an anonymous method as demonstrated in the Laravel docs:
// https://laravel.com/framework/docs/11.x/eloquent-factories#defining-relationships-within-factories
'actor_user_id' => User::factory(),
'actor_user_role' => UserRole::WIKI_MANAGER,
'type' => ReviewSubmissionActionType::SUBMITTED,
];
}

public function forActor(User $actor): static {
return $this->for($actor, 'actor');
}
}
26 changes: 26 additions & 0 deletions database/factories/ReviewSubmissionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Database\Factories;

use App\ReviewSubmission;
use App\Wiki;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends Factory<ReviewSubmission>
*/
class ReviewSubmissionFactory extends Factory {
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array {
return [
'wiki_id' => Wiki::factory(),
'additional_information' => 'This ReviewSubmission was created with a Factory. ' . fake()->paragraph(),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class() extends Migration {
/**
* Run the migrations.
*/
public function up(): void {
Schema::create('review_submissions', function (Blueprint $table) {
$table->id();
// Can't use the `foreignId()` method because the `wikis.id` column isn't an unsigned big integer
$table->unsignedInteger('wiki_id');
$table->foreign('wiki_id')->references('id')->on('wikis')
// Be explicit about the type of constraint restrictions,
// don't rely on the database's defaults as they could change.
// It's possible to have these restrict constraints as the Wiki model is currently only soft-deleted,
// so the `wiki_id` column should never be updated or deleted.
->restrictOnUpdate()
->restrictOnDelete();
$table->text('additional_information')->nullable();
$table->timestamps();
});
}

/**
* Reverse the migrations.
*/
public function down(): void {
Schema::dropIfExists('review_submissions');
}
};
Loading
Loading