From b73fa849ccad65b333edb9506aeeba3f0a1c0ac4 Mon Sep 17 00:00:00 2001 From: Ollie Date: Fri, 11 Sep 2026 17:14:06 +0100 Subject: [PATCH 1/3] Add migration, models, factories, and tests for review submissions Bug: T433682 --- app/Enums/ReviewSubmissionActionType.php | 13 +++ app/Enums/UserRole.php | 10 ++ app/ReviewSubmission.php | 95 +++++++++++++++++++ app/ReviewSubmissionAction.php | 77 +++++++++++++++ .../ReviewSubmissionActionFactory.php | 39 ++++++++ .../factories/ReviewSubmissionFactory.php | 26 +++++ ...000001_create_review_submissions_table.php | 36 +++++++ ...create_review_submission_actions_table.php | 42 ++++++++ .../Models/ReviewSubmissionActionTest.php | 85 +++++++++++++++++ tests/Feature/Models/ReviewSubmissionTest.php | 72 ++++++++++++++ 10 files changed, 495 insertions(+) create mode 100644 app/Enums/ReviewSubmissionActionType.php create mode 100644 app/Enums/UserRole.php create mode 100644 app/ReviewSubmission.php create mode 100644 app/ReviewSubmissionAction.php create mode 100644 database/factories/ReviewSubmissionActionFactory.php create mode 100644 database/factories/ReviewSubmissionFactory.php create mode 100644 database/migrations/2026_09_01_000001_create_review_submissions_table.php create mode 100644 database/migrations/2026_09_01_000002_create_review_submission_actions_table.php create mode 100644 tests/Feature/Models/ReviewSubmissionActionTest.php create mode 100644 tests/Feature/Models/ReviewSubmissionTest.php diff --git a/app/Enums/ReviewSubmissionActionType.php b/app/Enums/ReviewSubmissionActionType.php new file mode 100644 index 00000000..59d51607 --- /dev/null +++ b/app/Enums/ReviewSubmissionActionType.php @@ -0,0 +1,13 @@ + $actions + * @property-read int|null $actions_count + * @property-read ReviewSubmissionAction|null $latestAction + * @property-read Wiki|null $wiki + * + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission query() + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission whereAdditionalInformation($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmission whereWikiId($value) + * @method static \Database\Factories\ReviewSubmissionFactory factory($count = null, $state = []) + * + * @mixin \Eloquent + */ +class ReviewSubmission extends Model { + /** @use HasFactory */ + 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 + */ + 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(); + } +} diff --git a/app/ReviewSubmissionAction.php b/app/ReviewSubmissionAction.php new file mode 100644 index 00000000..d7c186c8 --- /dev/null +++ b/app/ReviewSubmissionAction.php @@ -0,0 +1,77 @@ +|ReviewSubmissionAction newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction query() + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereActorRole($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereReviewSubmissionId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereType($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereUserId($value) + * @method static \Database\Factories\ReviewSubmissionActionFactory factory($count = null, $state = []) + * + * @property-read User $actor + * + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereActorUserId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ReviewSubmissionAction whereActorUserRole($value) + * + * @mixin \Eloquent + */ +class ReviewSubmissionAction extends Model { + /** @use HasFactory */ + 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); + } +} diff --git a/database/factories/ReviewSubmissionActionFactory.php b/database/factories/ReviewSubmissionActionFactory.php new file mode 100644 index 00000000..7c115c95 --- /dev/null +++ b/database/factories/ReviewSubmissionActionFactory.php @@ -0,0 +1,39 @@ + + */ +class ReviewSubmissionActionFactory extends Factory { + /** + * Define the model's default state. + * + * @return array + */ + 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'); + } +} diff --git a/database/factories/ReviewSubmissionFactory.php b/database/factories/ReviewSubmissionFactory.php new file mode 100644 index 00000000..a67b2437 --- /dev/null +++ b/database/factories/ReviewSubmissionFactory.php @@ -0,0 +1,26 @@ + + */ +class ReviewSubmissionFactory extends Factory { + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array { + return [ + 'wiki_id' => Wiki::factory(), + 'additional_information' => 'This ReviewSubmission was created with a Factory. ' . fake()->paragraph(), + ]; + } +} diff --git a/database/migrations/2026_09_01_000001_create_review_submissions_table.php b/database/migrations/2026_09_01_000001_create_review_submissions_table.php new file mode 100644 index 00000000..8911b054 --- /dev/null +++ b/database/migrations/2026_09_01_000001_create_review_submissions_table.php @@ -0,0 +1,36 @@ +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'); + } +}; diff --git a/database/migrations/2026_09_01_000002_create_review_submission_actions_table.php b/database/migrations/2026_09_01_000002_create_review_submission_actions_table.php new file mode 100644 index 00000000..1b55743c --- /dev/null +++ b/database/migrations/2026_09_01_000002_create_review_submission_actions_table.php @@ -0,0 +1,42 @@ +id(); + $table->enum('type', ['submitted', 'review_started', 'approved', 'rejected', 'cancelled']); + $table->foreignId('review_submission_id') + ->constrained('review_submissions') + // Be explicit about the type of constraint restrictions, + // don't rely on the database's defaults as they could change + ->restrictOnUpdate() + ->restrictOnDelete(); + // Can't use the `foreignId()` method because the `users.id` column isn't an unsigned big integer + $table->unsignedInteger('actor_user_id'); + $table->foreign('actor_user_id')->references('id')->on('users') + // Be explicit about the type of constraint restrictions, + // don't rely on the database's defaults as they could change + ->restrictOnUpdate() + ->restrictOnDelete(); + // `actor_user_role` needs to be persisted here as an actor's user role might change + $table->enum('actor_user_role', ['wiki_manager', 'review_committee_admin']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void { + Schema::dropIfExists('review_submission_actions'); + } +}; diff --git a/tests/Feature/Models/ReviewSubmissionActionTest.php b/tests/Feature/Models/ReviewSubmissionActionTest.php new file mode 100644 index 00000000..eaf5c762 --- /dev/null +++ b/tests/Feature/Models/ReviewSubmissionActionTest.php @@ -0,0 +1,85 @@ +make(); + + $this->assertTrue($action->save()); + } + + /** + * A review submission action can retrieve the actor who performed this action. + */ + public function testRetrievingActor(): void { + $actor = User::factory()->create(); + + $action = ReviewSubmissionAction::factory() + ->forActor($actor) + ->create(); + + $this->assertTrue($action->actor->is($actor)); + } + + /** + * A review submission action can retrieve the review submission it belongs to. + */ + public function testRetrievingReviewSubmission(): void { + $submission = ReviewSubmission::factory()->create(); + + $action = ReviewSubmissionAction::factory() + ->for($submission) + ->create(); + + $this->assertTrue($action->reviewSubmission->is($submission)); + } + + /** + * A review submission action casts its actor_user_role attribute to a UserRole. + */ + public function testActorRoleCast(): void { + $action = ReviewSubmissionAction::factory()->create([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + ]); + + $this->assertSame(UserRole::WIKI_MANAGER, $action->actor_user_role); + } + + /** + * A review submission action casts its type to ReviewSubmissionActionType. + */ + public function testTypeCast(): void { + $action = ReviewSubmissionAction::factory()->create([ + 'type' => ReviewSubmissionActionType::REVIEW_STARTED, + ]); + + $this->assertSame(ReviewSubmissionActionType::REVIEW_STARTED, $action->type); + } + + /** + * A review submission action casts its timestamps to CarbonImmutable. + */ + public function testTimestampCasts(): void { + $action = ReviewSubmissionAction::factory()->create(); + + $this->assertInstanceOf(CarbonImmutable::class, $action->created_at); + $this->assertInstanceOf(CarbonImmutable::class, $action->updated_at); + } +} diff --git a/tests/Feature/Models/ReviewSubmissionTest.php b/tests/Feature/Models/ReviewSubmissionTest.php new file mode 100644 index 00000000..4521075a --- /dev/null +++ b/tests/Feature/Models/ReviewSubmissionTest.php @@ -0,0 +1,72 @@ +make(); + + $this->assertTrue($submission->save()); + } + + /** + * A review submission can retrieve all of its actions. + */ + public function testRetrievingActions(): void { + $submission = ReviewSubmission::factory()->create(); + + ReviewSubmissionAction::factory() + ->for($submission) + ->create(['type' => ReviewSubmissionActionType::SUBMITTED]); + + ReviewSubmissionAction::factory() + ->for($submission) + ->create(['type' => ReviewSubmissionActionType::REVIEW_STARTED]); + + $this->assertCount(2, $submission->actions); + $this->assertContainsOnlyInstancesOf(ReviewSubmissionAction::class, $submission->actions); + } + + /** + * A review submission can retrieve its latest action. + */ + public function testRetrievingLatestAction(): void { + $submission = ReviewSubmission::factory()->create(); + + ReviewSubmissionAction::factory() + ->for($submission) + ->create(['type' => ReviewSubmissionActionType::SUBMITTED]); + + $latestAction = ReviewSubmissionAction::factory() + ->for($submission) + ->create(['type' => ReviewSubmissionActionType::REVIEW_STARTED]); + + $submission->refresh(); + + $this->assertTrue($submission->latestAction->is($latestAction)); + } + + /** + * A review submission casts its timestamps to CarbonImmutable. + */ + public function testTimestampCasts(): void { + $action = ReviewSubmission::factory()->create(); + + $this->assertInstanceOf(CarbonImmutable::class, $action->created_at); + $this->assertInstanceOf(CarbonImmutable::class, $action->updated_at); + } +} From 08996829f2e1a5d299c959facee799f3576db245 Mon Sep 17 00:00:00 2001 From: Ollie Date: Fri, 11 Sep 2026 17:15:32 +0100 Subject: [PATCH 2/3] Create and register ReviewSubmissionSeeder --- database/seeds/DatabaseSeeder.php | 4 + database/seeds/ReviewSubmissionSeeder.php | 172 ++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 database/seeds/ReviewSubmissionSeeder.php diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 89690c68..625b49d9 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -1,5 +1,8 @@ call(WikiDbsSeeder::class); $this->call(QueryserviceNamespacesSeeder::class); $this->call(WikisSeeder::class); + $this->call(ReviewSubmissionSeeder::class); Model::reguard(); } } diff --git a/database/seeds/ReviewSubmissionSeeder.php b/database/seeds/ReviewSubmissionSeeder.php new file mode 100644 index 00000000..b0d0dc62 --- /dev/null +++ b/database/seeds/ReviewSubmissionSeeder.php @@ -0,0 +1,172 @@ +createSubmittedReviewSubmission(); + $this->createReviewStartedReviewSubmission(); + $this->createAcceptedReviewSubmission(); + $this->createRejectedReviewSubmission(); + $this->createSubmittedThenCancelledReviewSubmission(); + $this->createReviewStartedThenCancelledReviewSubmission(); + } + + public function createSubmittedReviewSubmission(): void { + $wiki = Wiki::firstOrCreate([ + 'sitename' => 'ReviewSubmissionSeeder::createSubmittedReviewSubmission()', + 'domain' => 'ReviewSubmissionSeeder.createSubmittedReviewSubmission.wbaas.dev', + 'description' => 'Wiki for ReviewSubmissionSeeder::createSubmittedReviewSubmission()', + ]); + + $submission = ReviewSubmission::query()->firstOrCreate(['wiki_id' => $wiki->id]); + + if ($submission->wasRecentlyCreated) { + $submission->actions()->saveMany([ + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::SUBMITTED, + ]), + ]); + } + } + + public function createReviewStartedReviewSubmission(): void { + $wiki = Wiki::firstOrCreate([ + 'sitename' => 'ReviewSubmissionSeeder::createReviewStartedReviewSubmission()', + 'domain' => 'ReviewSubmissionSeeder.createReviewStartedReviewSubmission.wbaas.dev', + 'description' => 'Wiki for ReviewSubmissionSeeder::createReviewStartedReviewSubmission()', + ]); + + $submission = ReviewSubmission::query()->firstOrCreate(['wiki_id' => $wiki->id]); + + if ($submission->wasRecentlyCreated) { + $submission->actions()->saveMany([ + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::SUBMITTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::REVIEW_COMMITTEE_ADMIN, + 'type' => ReviewSubmissionActionType::REVIEW_STARTED, + ]), + ]); + } + } + + public function createAcceptedReviewSubmission(): void { + $wiki = Wiki::firstOrCreate([ + 'sitename' => 'ReviewSubmissionSeeder::createApprovedReviewSubmission()', + 'domain' => 'ReviewSubmissionSeeder.createApprovedReviewSubmission.wbaas.dev', + 'description' => 'Wiki for ReviewSubmissionSeeder::createApprovedReviewSubmission()', + ]); + + $submission = ReviewSubmission::query()->firstOrCreate(['wiki_id' => $wiki->id]); + + if ($submission->wasRecentlyCreated) { + $submission->actions()->saveMany([ + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::SUBMITTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::REVIEW_COMMITTEE_ADMIN, + 'type' => ReviewSubmissionActionType::REVIEW_STARTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::REVIEW_COMMITTEE_ADMIN, + 'type' => ReviewSubmissionActionType::APPROVED, + ]), + ]); + } + } + + public function createRejectedReviewSubmission(): void { + $wiki = Wiki::firstOrCreate([ + 'sitename' => 'ReviewSubmissionSeeder::createRejectedReviewSubmission()', + 'domain' => 'ReviewSubmissionSeeder.createRejectedReviewSubmission.wbaas.dev', + 'description' => 'Wiki for ReviewSubmissionSeeder::createRejectedReviewSubmission()', + ]); + + $submission = ReviewSubmission::query()->firstOrCreate(['wiki_id' => $wiki->id]); + + if ($submission->wasRecentlyCreated) { + $submission->actions()->saveMany([ + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::SUBMITTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::REVIEW_COMMITTEE_ADMIN, + 'type' => ReviewSubmissionActionType::REVIEW_STARTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::REVIEW_COMMITTEE_ADMIN, + 'type' => ReviewSubmissionActionType::REJECTED, + ]), + ]); + } + } + + public function createSubmittedThenCancelledReviewSubmission(): void { + $wiki = Wiki::firstOrCreate([ + 'sitename' => 'ReviewSubmissionSeeder::createSubmittedThenCancelledReviewSubmission()', + 'domain' => 'ReviewSubmissionSeeder.createSubmittedThenCancelledReviewSubmission.wbaas.dev', + 'description' => 'Wiki for ReviewSubmissionSeeder::createSubmittedThenCancelledReviewSubmission()', + ]); + + $submission = ReviewSubmission::query()->firstOrCreate(['wiki_id' => $wiki->id]); + + if ($submission->wasRecentlyCreated) { + $submission->actions()->saveMany([ + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::SUBMITTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::CANCELLED, + ]), + ]); + } + } + + public function createReviewStartedThenCancelledReviewSubmission(): void { + $wiki = Wiki::firstOrCreate([ + 'sitename' => 'ReviewSubmissionSeeder::createReviewStartedThenCancelledReviewSubmission()', + 'domain' => 'ReviewSubmissionSeeder.createReviewStartedThenCancelledReviewSubmission.wbaas.dev', + 'description' => 'Wiki for ReviewSubmissionSeeder::createReviewStartedThenCancelledReviewSubmission()', + ]); + + $submission = ReviewSubmission::query()->firstOrCreate(['wiki_id' => $wiki->id]); + + if ($submission->wasRecentlyCreated) { + $submission->actions()->saveMany([ + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::SUBMITTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::REVIEW_COMMITTEE_ADMIN, + 'type' => ReviewSubmissionActionType::REVIEW_STARTED, + ]), + ReviewSubmissionAction::factory()->make([ + 'actor_user_role' => UserRole::WIKI_MANAGER, + 'type' => ReviewSubmissionActionType::CANCELLED, + ]), + ]); + } + } +} From 6f9d3cbaeb4516b175a179a76dcf8ed545f0c3b2 Mon Sep 17 00:00:00 2001 From: Ollie Date: Fri, 11 Sep 2026 17:16:55 +0100 Subject: [PATCH 3/3] Add reviewSubmissions() relationship to App\Wiki and update docblock --- app/Wiki.php | 52 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/app/Wiki.php b/app/Wiki.php index 2c89ff02..f8ec84e3 100644 --- a/app/Wiki.php +++ b/app/Wiki.php @@ -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 $reviewSubmissions + * @property-read int|null $review_submissions_count + * @property-read Collection $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 $wikiEntityImports + * @property-read int|null $wiki_entity_imports_count + * @property-read WikiProfile|null $wikiLatestProfile + * @property-read WikiLifecycleEvents|null $wikiLifecycleEvents + * @property-read Collection $wikiManagers * @property-read int|null $wiki_managers_count + * @property-read Collection $wikiManagersWithEmail + * @property-read int|null $wiki_managers_with_email_count + * @property-read Collection $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|Wiki newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Wiki newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Wiki onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|Wiki query() + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereDomain($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereIsFeatured($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereSitename($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki whereWikiDeletionReason($value) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki withTrashed(bool $withTrashed = true) + * @method static \Illuminate\Database\Eloquent\Builder|Wiki withoutTrashed() * * @mixin \Eloquent */ @@ -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']); }