From 5078756988b95c266d7dca4de42b9be62e3d04a6 Mon Sep 17 00:00:00 2001 From: Thomas Arrow Date: Fri, 31 Jul 2026 11:16:13 +0100 Subject: [PATCH] Prototype: Add Models, Controllers for Suspensions and Submissions Do not merge, only a prototype for consideration of the architecture of the implmentation plan proposed by the PM Still to be done: - implementation ReviewSubmission controller - MakeExistingWikisTemporaryJob - NightlyRunScheduledWikiSuspensionJobs --- app/ActiveSuspension.php | 13 +++ .../ActiveSuspensionController.php | 56 +++++++++++ .../ReviewSubmissionController.php | 55 +++++++++++ .../ScheduledSuspensionController.php | 61 ++++++++++++ app/ReviewSubmission.php | 10 ++ app/ScheduledSuspension.php | 13 +++ app/Wiki.php | 4 + ...3148_create_scheduled_suspension_table.php | 30 ++++++ ..._103156_create_active_suspension_table.php | 30 ++++++ ..._103206_create_review_submission_table.php | 29 ++++++ routes/api.php | 5 + .../Routes/ActiveSuspensionControllerTest.php | 62 ++++++++++++ .../Routes/ReviewSubmissionControllerTest.php | 52 ++++++++++ .../ScheduledSuspensionControllerTest.php | 99 +++++++++++++++++++ 14 files changed, 519 insertions(+) create mode 100644 app/ActiveSuspension.php create mode 100644 app/Http/Controllers/ActiveSuspensionController.php create mode 100644 app/Http/Controllers/ReviewSubmissionController.php create mode 100644 app/Http/Controllers/ScheduledSuspensionController.php create mode 100644 app/ReviewSubmission.php create mode 100644 app/ScheduledSuspension.php create mode 100644 database/migrations/2026_07_31_103148_create_scheduled_suspension_table.php create mode 100644 database/migrations/2026_07_31_103156_create_active_suspension_table.php create mode 100644 database/migrations/2026_07_31_103206_create_review_submission_table.php create mode 100644 tests/Routes/ActiveSuspensionControllerTest.php create mode 100644 tests/Routes/ReviewSubmissionControllerTest.php create mode 100644 tests/Routes/ScheduledSuspensions/ScheduledSuspensionControllerTest.php diff --git a/app/ActiveSuspension.php b/app/ActiveSuspension.php new file mode 100644 index 00000000..0346e72b --- /dev/null +++ b/app/ActiveSuspension.php @@ -0,0 +1,13 @@ + $request->input('since'), + 'wiki_id' => $request->input('wiki_id') + ]); + } + + /** + * Display the specified resource. + */ + public function show(ActiveSuspension $activeSuspension) + { + // Possible for anyone + // TODO: implement + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ActiveSuspension $activeSuspension) + { + // Only possible for admin + // TODO: implement + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ActiveSuspension $suspension) + { + $suspension->first()->delete(); + } +} diff --git a/app/Http/Controllers/ReviewSubmissionController.php b/app/Http/Controllers/ReviewSubmissionController.php new file mode 100644 index 00000000..d68d9fde --- /dev/null +++ b/app/Http/Controllers/ReviewSubmissionController.php @@ -0,0 +1,55 @@ +status = 'submitted'; + $submission->wiki_id = $request->input('wiki_id'); + $submission->save(); + } + + /** + * Display the specified resource. + */ + public function show(ReviewSubmission $reviewSubmission) + { + // Possible for admin or specifically authed user + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ReviewSubmission $reviewSubmission) + { + // Only possible for admin to pick up/approv, normal Wikimanager can cancel + + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ReviewSubmission $reviewSubmission) + { + // Possible for admin only + } +} diff --git a/app/Http/Controllers/ScheduledSuspensionController.php b/app/Http/Controllers/ScheduledSuspensionController.php new file mode 100644 index 00000000..4d64b57d --- /dev/null +++ b/app/Http/Controllers/ScheduledSuspensionController.php @@ -0,0 +1,61 @@ + $request->input('active_from'), + 'wiki_id' => $request->input('wiki_id') + ]); + } + + /** + * Display the specified resource. + */ + public function show( ScheduledSuspension $suspension ) + { + return $suspension->get(); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, ScheduledSuspension $suspension) + { + $suspension->first()->update([ + 'active_from' => $request->input('active_from') + ]); + + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ScheduledSuspension $suspension) + { + $suspension->first()->delete(); + } + + public function showByWiki( Wiki $wiki ) { + return $wiki->first()->scheduledSuspension; + } + +} diff --git a/app/ReviewSubmission.php b/app/ReviewSubmission.php new file mode 100644 index 00000000..a03ae106 --- /dev/null +++ b/app/ReviewSubmission.php @@ -0,0 +1,10 @@ +hasMany(WikiSetting::class); } + public function scheduledSuspension(): HasOne { + return $this->hasOne(ScheduledSuspension::class); + } + public function publicSettings() { return $this->settings()->whereIn('name', [ diff --git a/database/migrations/2026_07_31_103148_create_scheduled_suspension_table.php b/database/migrations/2026_07_31_103148_create_scheduled_suspension_table.php new file mode 100644 index 00000000..4f580a91 --- /dev/null +++ b/database/migrations/2026_07_31_103148_create_scheduled_suspension_table.php @@ -0,0 +1,30 @@ +id(); + $table->timestamps(); + $table->foreignId('wiki_id'); + $table->date('active_from'); + $table->enum('reason', ['expiry', 'hp_violation', 'tou_violation']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('scheduled_suspension'); + } +}; diff --git a/database/migrations/2026_07_31_103156_create_active_suspension_table.php b/database/migrations/2026_07_31_103156_create_active_suspension_table.php new file mode 100644 index 00000000..b2b8f025 --- /dev/null +++ b/database/migrations/2026_07_31_103156_create_active_suspension_table.php @@ -0,0 +1,30 @@ +id(); + $table->timestamps(); + $table->foreignId('wiki_id'); + $table->date('since'); + $table->enum('reason', ['expiry', 'hp_violation', 'tou_violation']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('active_suspension'); + } +}; diff --git a/database/migrations/2026_07_31_103206_create_review_submission_table.php b/database/migrations/2026_07_31_103206_create_review_submission_table.php new file mode 100644 index 00000000..19f846c0 --- /dev/null +++ b/database/migrations/2026_07_31_103206_create_review_submission_table.php @@ -0,0 +1,29 @@ +id(); + $table->timestamps(); + $table->foreignId('wiki_id'); + $table->enum('status', ['submitted', 'in_review', 'approved', 'rejected', 'cancelled'])->required(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('review_submission'); + } +}; diff --git a/routes/api.php b/routes/api.php index 0c69b187..ae2258f0 100644 --- a/routes/api.php +++ b/routes/api.php @@ -65,8 +65,13 @@ $router->get('v1/policies/{policy_type}/upcoming', ['uses' => 'PolicyController@getUpcomingPolicyByType']); $router->get('v1/policies/{policy_type}/by_active_from/{active_from}', ['uses' => 'PolicyController@getPolicyByTypeAndActiveFrom']); $router->get('v1/policies/{policy_type}', ['uses' => 'PoliciesController@getPoliciesByType']); + $router->apiResource('v1/scheduledSuspension', 'ScheduledSuspensionController'); + $router->apiResource('v1/activeSuspension', 'ActiveSuspensionController')->only('destroy', 'store'); + $router->apiResource('v1/reviewSubmission', 'ReviewSubmissionController')->only('store', 'update'); + $router->get('v1/scheduledSuspension/wiki/{wiki}', ['uses' => 'ScheduledSuspensionController@showByWiki']); $router->apiResource('wiki', 'PublicWikiController')->only(['index', 'show']); $router->apiResource('reusePrototype', 'PublicWikiController')->only(['index']); $router->apiResource('wikiConversionData', 'ConversionMetricController')->only(['index']); + }); diff --git a/tests/Routes/ActiveSuspensionControllerTest.php b/tests/Routes/ActiveSuspensionControllerTest.php new file mode 100644 index 00000000..69d91cb3 --- /dev/null +++ b/tests/Routes/ActiveSuspensionControllerTest.php @@ -0,0 +1,62 @@ +wiki = Wiki::factory()->create(); + } + + protected function tearDown(): void { + parent::tearDown(); + } + + public function testGetAdminCreateSuspension(): void { + $this->json( + 'POST', + $this->baseRoute, + $data = [ + 'since' => '2028-01-01', + 'wiki_id' => $this->wiki->id + ]); + + $expectedSuspension = ActiveSuspension::where( + [ + 'since' => '2028-01-01', + 'wiki_id' => $this->wiki->id + ] + ); + $this->assertTrue($expectedSuspension->exists()); + } + + public function testGetAdminRemoveSuspension(): void { + $suspension = ActiveSuspension::create([ + 'since' => '2029-01-01', + 'wiki_id' => $this->wiki->id + ]); + $suspension2 = ActiveSuspension::create([ + 'since' => '2029-02-01', + 'wiki_id' => $this->wiki->id + ]); + $suspension3 = ActiveSuspension::create([ + 'since' => '2029-03-01', + 'wiki_id' => $this->wiki->id + ]); + $this->json('DELETE', $this->baseRoute . '/' . $suspension->id ) + ->assertStatus(200); + $this->assertModelMissing($suspension); + } + +} diff --git a/tests/Routes/ReviewSubmissionControllerTest.php b/tests/Routes/ReviewSubmissionControllerTest.php new file mode 100644 index 00000000..99bca38e --- /dev/null +++ b/tests/Routes/ReviewSubmissionControllerTest.php @@ -0,0 +1,52 @@ +wiki = Wiki::factory()->create(); + } + + protected function tearDown(): void { + parent::tearDown(); + } + + public function testManagerCreateSubmission(): void { + $this->json( + 'POST', + $this->baseRoute, + $data = [ + 'wiki_id' => $this->wiki->id + ]); + + $expectedSubmission = ReviewSubmission::where( + [ + 'wiki_id' => $this->wiki->id, + 'status' => 'submitted' + ] + ); + $this->assertTrue($expectedSubmission->exists()); + } + + public function testManagerCancelSubmission(): void { + + } + + public function testGetAdminStartPendingReview(): void { + + } + +} diff --git a/tests/Routes/ScheduledSuspensions/ScheduledSuspensionControllerTest.php b/tests/Routes/ScheduledSuspensions/ScheduledSuspensionControllerTest.php new file mode 100644 index 00000000..577ceb91 --- /dev/null +++ b/tests/Routes/ScheduledSuspensions/ScheduledSuspensionControllerTest.php @@ -0,0 +1,99 @@ +wiki = Wiki::factory()->create(); + } + + protected function tearDown(): void { + parent::tearDown(); + } + + public function testGetEmpty(): void { + $this->json('GET', $this->baseRoute ) + ->assertExactJson([]) + ->assertStatus(200); + } + + public function testGetIndexWithContent(): void { + ScheduledSuspension::create([ + 'active_from' => '2028-01-01', + 'wiki_id' => $this->wiki->id + ]); + $this->json('GET', $this->baseRoute ) + ->assertJsonCount(1) + ->assertStatus(200); + } + + public function testGetAdminCreateSuspension(): void { + $this->json( + 'POST', + $this->baseRoute, + $data = [ + 'active_from' => '2028-01-01', + 'wiki_id' => $this->wiki->id + ]); + $this->assertModelExists(ScheduledSuspension::first()); + } + + public function testGetSuspensionById(): void { + $suspension = ScheduledSuspension::create([ + 'active_from' => '2029-01-01', + 'wiki_id' => $this->wiki->id + ]); + $this->json('GET', $this->baseRoute . '/' . $suspension->id ) + ->assertStatus(200) + ->assertJsonFragment(['active_from' => '2029-01-01']); + } + + public function testGetAdminUpdateSuspension(): void { + $suspension = ScheduledSuspension::create([ + 'active_from' => '2029-01-01', + 'wiki_id' => $this->wiki->id + ]); + $this->json('PUT', $this->baseRoute . '/' . $suspension->id, $data = ['active_from' => '2028-01-01'] ) + ->assertStatus(200); + $suspension->refresh(); + $this->assertEquals( '2028-01-01', $suspension->active_from ); + + } + + public function testGetAdminRemoveSuspension(): void { + $suspension = ScheduledSuspension::create([ + 'active_from' => '2029-01-01', + 'wiki_id' => $this->wiki->id + ]); + $this->json('DELETE', $this->baseRoute . '/' . $suspension->id ) + ->assertStatus(200); + $this->assertModelMissing($suspension); + } + + public function testGetActiveSuspensionsByWiki(): void { + $suspension = ScheduledSuspension::create([ + 'active_from' => '2029-01-01', + 'wiki_id' => $this->wiki->id + ]); + $response = $this->json('GET', $this->baseRoute . '/wiki/' . $this->wiki->id ); + $response + ->assertStatus(200) + ->assertJsonFragment(['active_from' => '2029-01-01']); + } + + +}