-
Notifications
You must be signed in to change notification settings - Fork 3
Create Model for Term Of Use acceptance #977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d768691
c458ee1
7aceaa9
3c8924c
dad1cd7
47e7875
456bf9d
89eeda0
bf56e3e
95a05e0
eb6640e
33bb2f0
90954d3
39440e3
5e21e2f
0eb8579
e816ab6
e6e779b
734a52a
2652871
0b54f8d
b5979d7
7f30a37
43d6608
9ab90cc
f095740
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php | ||
|
|
||
| namespace App\Jobs; | ||
|
|
||
| use App\TermsOfUseVersion; | ||
| use Illuminate\Bus\Batchable; | ||
| use Illuminate\Foundation\Bus\Dispatchable; | ||
| use Illuminate\Support\Facades\Log; | ||
| use Throwable; | ||
|
|
||
| class CreateFirstTermsOfUseVersionJob extends Job { | ||
| use Batchable; | ||
| use Dispatchable; | ||
|
|
||
| public function handle(): void { | ||
| try { | ||
| TermsOfUseVersion::create([ | ||
| 'version' => '2022-01-01', | ||
| 'active' => true, | ||
| ]); | ||
| } catch (Throwable $exception) { | ||
| Log::error("Failure creating initial Terms of Use version: {$exception->getMessage()}"); | ||
| $this->fail($exception); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| <?php | ||
|
|
||
| namespace App\Jobs; | ||
|
|
||
| use App\TermsOfUseVersion; | ||
| use App\User; | ||
| use App\UserTermsOfUseAcceptance; | ||
| use Illuminate\Bus\Batchable; | ||
| use Illuminate\Foundation\Bus\Dispatchable; | ||
| use Illuminate\Support\Facades\Log; | ||
| use Throwable; | ||
|
|
||
| /** | ||
| * Bug: T401165 https://phabricator.wikimedia.org/T401165 | ||
| * Job to record Terms of Use acceptance for all preexisting users. | ||
| * This job should only be run ONCE to seed the data for terms of use users have agreed to before we started tracking it explicitly. | ||
| * This job iterates through all users and creates a UserTermsOfUseAcceptance record | ||
| * for each, using the latest (only) Terms of Use version and the user's creation date as ToU acceptance date. | ||
| * Errors during processing are logged and the job is marked as failed if accepting the terms of use for any user fails. | ||
| */ | ||
| class UserTouAcceptanceJob extends Job { | ||
| use Batchable; | ||
| use Dispatchable; | ||
|
|
||
| public function handle(): void { | ||
| $users = User::all(); | ||
| foreach ($users as $user) { | ||
| try { | ||
| UserTermsOfUseAcceptance::create([ | ||
| 'user_id' => $user->id, | ||
| 'tou_version' => TermsOfUseVersion::latestActiveVersion()->version, | ||
| 'tou_accepted_at' => $user->created_at, | ||
| ]); | ||
| } catch (Throwable $exception) { | ||
| Log::error("Failure processing user {$user->email} for UserTouAcceptanceJob: {$exception->getMessage()}"); | ||
| $this->fail($exception); | ||
tarrow marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
|
|
||
| namespace App; | ||
|
|
||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||
| use Illuminate\Database\Eloquent\Model; | ||
|
|
||
| /** | ||
| * Bug: T401165 https://phabricator.wikimedia.org/T401165 | ||
| * Be mindful that multiple ToU versions may exist over time, | ||
| * but only one should be active at a time. | ||
| */ | ||
| class TermsOfUseVersion extends Model { | ||
| use HasFactory; | ||
|
|
||
| protected $table = 'tou_versions'; | ||
|
|
||
| const FIELDS = [ | ||
| 'version', | ||
| 'active', | ||
| ]; | ||
|
|
||
| protected $fillable = self::FIELDS; | ||
|
|
||
| protected $visible = self::FIELDS; | ||
|
|
||
| protected $casts = [ | ||
| 'version' => 'string', | ||
| 'active' => 'boolean', | ||
| ]; | ||
|
|
||
| public static function latestActiveVersion(): ?self { | ||
| return self::query()->where('active', true)->latest()->first(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php | ||
|
|
||
| namespace App; | ||
|
|
||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||
| use Illuminate\Database\Eloquent\Model; | ||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||
|
|
||
| class UserTermsOfUseAcceptance extends Model { | ||
| use HasFactory; | ||
|
|
||
| public const FIELDS = [ | ||
| 'user_id', | ||
| 'tou_version', | ||
| 'tou_accepted_at', | ||
tarrow marked this conversation as resolved.
Show resolved
Hide resolved
tarrow marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ]; | ||
|
|
||
| protected $fillable = self::FIELDS; | ||
|
|
||
| protected $visible = self::FIELDS; | ||
|
|
||
| protected $casts = [ | ||
| 'tou_version' => 'string', | ||
| 'tou_accepted_at' => 'datetime', | ||
| ]; | ||
|
|
||
| protected $table = 'tou_acceptances'; | ||
|
|
||
| public function user(): BelongsTo { | ||
| return $this->belongsTo(User::class); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| <?php | ||
|
|
||
| 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('tou_acceptances', function (Blueprint $table) { | ||
| $table->id(); | ||
| $table->unsignedInteger('user_id'); | ||
| $table->string('tou_version', 10); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to be linked to the |
||
| $table->timestamp('tou_accepted_at'); | ||
| $table->timestamps(); | ||
| $table->unique(['user_id', 'tou_version']); | ||
| $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Reverse the migrations. | ||
| */ | ||
| public function down(): void { | ||
| Schema::dropIfExists('tou_acceptances'); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php | ||
|
|
||
| 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('tou_versions', function (Blueprint $table) { | ||
| $table->id(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this ID ever used? It's not used in the |
||
| $table->string('version')->unique(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, as the |
||
| $table->boolean('active')->default(false); | ||
| $table->timestamps(); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Reverse the migrations. | ||
| */ | ||
| public function down(): void { | ||
| Schema::dropIfExists('tou_versions'); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <?php | ||
|
|
||
| namespace Tests\Jobs; | ||
|
|
||
| use App\Jobs\CreateFirstTermsOfUseVersionJob; | ||
| use Illuminate\Foundation\Testing\RefreshDatabase; | ||
| use Tests\TestCase; | ||
|
|
||
| class CreateFirstTermsOfUseVersionJobTest extends TestCase { | ||
| use RefreshDatabase; | ||
|
|
||
| public function testCreateFirstTermsOfUseVersionJob(): void { | ||
| $this->assertDatabaseCount('tou_versions', 0); | ||
|
|
||
| (new CreateFirstTermsOfUseVersionJob)->handle(); | ||
|
|
||
| $this->assertDatabaseHas('tou_versions', [ | ||
| 'version' => '2022-01-01', | ||
| 'active' => true, | ||
| ]); | ||
| } | ||
| } |
tarrow marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php | ||
|
|
||
| namespace Tests\Jobs; | ||
|
|
||
| use App\Jobs\CreateFirstTermsOfUseVersionJob; | ||
| use App\Jobs\UserCreateJob; | ||
| use App\TermsOfUseVersion; | ||
| use App\UserTermsOfUseAcceptance; | ||
| use Illuminate\Foundation\Testing\RefreshDatabase; | ||
| use Tests\TestCase; | ||
|
|
||
| class UserTermsOfUseAcceptanceTest extends TestCase { | ||
| use RefreshDatabase; | ||
|
|
||
| public function testUserCreationCreatesTouAcceptance(): void { | ||
| (new CreateFirstTermsOfUseVersionJob)->handle(); | ||
| $email = 'test+' . uniqid('', true) . '@example.com'; | ||
| $user = (new UserCreateJob($email, 'thisisapassword123', true))->handle(); | ||
|
|
||
| $this->assertDatabaseHas('tou_acceptances', [ | ||
| 'user_id' => $user->id, | ||
| 'tou_version' => TermsOfUseVersion::latestActiveVersion()->version, | ||
| ]); | ||
|
|
||
| $rows = UserTermsOfUseAcceptance::where('user_id', $user->id)->get(); | ||
| $this->assertCount(1, $rows); | ||
| $acceptance = $rows->first(); | ||
|
|
||
| $this->assertSame(TermsOfUseVersion::latestActiveVersion()->version, $acceptance->tou_version); | ||
| $this->assertNotNull($acceptance->tou_accepted_at); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <?php | ||
|
|
||
| namespace Tests\Jobs; | ||
|
|
||
| use App\Jobs\CreateFirstTermsOfUseVersionJob; | ||
| use App\Jobs\UserTouAcceptanceJob; | ||
| use App\TermsOfUseVersion; | ||
| use App\User; | ||
| use App\UserTermsOfUseAcceptance; | ||
| use Illuminate\Foundation\Testing\RefreshDatabase; | ||
| use Illuminate\Support\Carbon; | ||
| use Tests\TestCase; | ||
|
|
||
| class UserTouAcceptanceJobTest extends TestCase { | ||
| use RefreshDatabase; | ||
|
|
||
| public function testTouAcceptanceJob(): void { | ||
| $t1 = Carbon::parse('2025-01-01 10:00:00'); | ||
| $t2 = Carbon::parse('2025-01-02 11:00:00'); | ||
| $t3 = Carbon::parse('2025-01-03 12:00:00'); | ||
|
|
||
| $u1 = User::factory()->create(['created_at' => $t1]); | ||
| $u2 = User::factory()->create(['created_at' => $t2]); | ||
| $u3 = User::factory()->create(['created_at' => $t3]); | ||
|
|
||
| (new CreateFirstTermsOfUseVersionJob)->handle(); | ||
| (new UserTouAcceptanceJob)->handle(); | ||
|
|
||
| $latest = TermsOfUseVersion::latestActiveVersion()->version; | ||
|
|
||
| $this->assertDatabaseHas('tou_acceptances', ['user_id' => $u1->id, 'tou_version' => $latest]); | ||
| $this->assertDatabaseHas('tou_acceptances', ['user_id' => $u2->id, 'tou_version' => $latest]); | ||
| $this->assertDatabaseHas('tou_acceptances', ['user_id' => $u3->id, 'tou_version' => $latest]); | ||
|
|
||
| $this->assertTrue($t1->equalTo(UserTermsOfUseAcceptance::where('user_id', $u1->id)->firstOrFail()->tou_accepted_at)); | ||
| $this->assertTrue($t2->equalTo(UserTermsOfUseAcceptance::where('user_id', $u2->id)->firstOrFail()->tou_accepted_at)); | ||
| $this->assertTrue($t3->equalTo(UserTermsOfUseAcceptance::where('user_id', $u3->id)->firstOrFail()->tou_accepted_at)); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.