Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d768691
Create Model for Term Of Use acceptance
dati18 Sep 25, 2025
c458ee1
minor fix
dati18 Sep 29, 2025
7aceaa9
Add missing Migration for tou_acceptances table
dati18 Sep 29, 2025
3c8924c
add lastest() method to get lastest version of ToU
dati18 Sep 29, 2025
dad1cd7
Add test case
dati18 Sep 30, 2025
47e7875
Add test for ToU acceptance when user was created
dati18 Sep 30, 2025
456bf9d
Fix test
dati18 Sep 30, 2025
89eeda0
fix linting errors
dati18 Sep 30, 2025
bf56e3e
fix linting errors
dati18 Sep 30, 2025
95a05e0
fix linting errors
dati18 Sep 30, 2025
eb6640e
Fix typo in classes' names
dati18 Oct 8, 2025
33bb2f0
Add Job to generate 'tou_version' and 'tou_accepted_at' for existing …
dati18 Oct 8, 2025
90954d3
fix linting errors
dati18 Oct 8, 2025
39440e3
Change UserTermsOfUseAcceptances' relation with 'user_id'
dati18 Oct 9, 2025
5e21e2f
Minor fix
dati18 Oct 9, 2025
0eb8579
fix UserTouAcceptanceJobTest
dati18 Oct 9, 2025
e816ab6
fix linting error
dati18 Oct 9, 2025
e6e779b
Refactor: Change TermsOfUseVersion from enum to Model
dati18 Oct 14, 2025
734a52a
Fix: Remove hard dependency on pre-seeded ToU version
dati18 Oct 14, 2025
2652871
fix linting errors
dati18 Oct 14, 2025
0b54f8d
fix linting error
dati18 Oct 14, 2025
b5979d7
minor changes
dati18 Oct 15, 2025
7f30a37
Fix: removed unnecessary fields
dati18 Oct 15, 2025
43d6608
fix testing wrong version
dati18 Oct 15, 2025
9ab90cc
fix linting error
dati18 Oct 15, 2025
f095740
rename the method that retrieve latest active version of ToU
dati18 Oct 15, 2025
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
8 changes: 8 additions & 0 deletions app/Jobs/UserCreateJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace App\Jobs;

use App\TermsOfUseVersion;
use App\User;
use App\UserTermsOfUseAcceptance;
use Illuminate\Support\Facades\Hash;

class UserCreateJob extends Job {
Expand Down Expand Up @@ -30,6 +32,12 @@ public function handle() {
'verified' => $this->verified,
]);

UserTermsOfUseAcceptance::create([
'user_id' => $user->id,
'tou_version' => TermsOfUseVersion::latest(),
'tou_accepted_at' => now(),
]);

return $user;
}
}
42 changes: 42 additions & 0 deletions app/Jobs/UserTouAcceptanceJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Jobs;

use App\TermsOfUseVersion;
use App\User;
use App\UserTermsOfUseAcceptance;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Throwable;

class UserTouAcceptanceJob extends Job {
use Batchable;
use Dispatchable;

private $users;

public function __construct($users) {
$this->users = $users;
}
public function handle(): void {
$this->users = User::all();
Comment thread
tarrow marked this conversation as resolved.
Outdated
foreach ($this->users as $user) {
try {
UserTermsOfUseAcceptance::create([
'user_id' => $user->id,
'tou_version' => TermsOfUseVersion::latest(),
'tou_accepted_at' => $user->created_at,
]);
} catch (Throwable $exception) {
Log::error('Failure processing user ' . $user->getAttribute('email') . ' for UserTouAcceptanceJob: ' . $exception->getMessage());
$this->fail($exception);
Comment thread
tarrow marked this conversation as resolved.
}
}
}
}
26 changes: 26 additions & 0 deletions app/TermsOfUseVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App;

enum TermsOfUseVersion: string {
// case v2 = 'yyyy-mm-dd';
// case v1 = 'yyyy-mm-dd';
case v0 = '2025-08-21';

public static function latest(): self {
$latestVersion = self::v0;
$latestNum = 0;
foreach (self::cases() as $case) {
if (!str_starts_with($case->name, 'v')) {
continue;
}
$n = (int) substr($case->name, 1);
if ($n > $latestNum) {
$latestNum = $n;
$latestVersion = $case;
}
}

return $latestVersion;
}
}
4 changes: 4 additions & 0 deletions app/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ public function managesWikis(): \Illuminate\Database\Eloquent\Relations\BelongsT
return $this->belongsToMany(Wiki::class, 'wiki_managers');
}

public function touAcceptances(): \Illuminate\Database\Eloquent\Relations\BelongsToMany {
return $this->belongsToMany(UserTermsOfUseAcceptance::class, 'tou_acceptances');
Comment thread
tarrow marked this conversation as resolved.
Outdated
}

public function hasVerifiedEmail() {
return (bool) $this->verified;
}
Expand Down
27 changes: 27 additions & 0 deletions app/UserTermsOfUseAcceptance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class UserTermsOfUseAcceptance extends Model {
use HasFactory;

const FIELDS = [
'user_id',
'tou_version',
'tou_accepted_at',
Comment thread
tarrow marked this conversation as resolved.
Comment thread
tarrow marked this conversation as resolved.
];

protected $fillable = self::FIELDS;

protected $visible = self::FIELDS;

protected $casts = [
'tou_version' => TermsOfUseVersion::class,
'tou_accepted_at' => 'datetime',
];

protected $table = 'tou_acceptances';
}
29 changes: 29 additions & 0 deletions database/migrations/2025_09_29_194758_tou_acceptances.php
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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be linked to the tou_versions table.

$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');
}
};
37 changes: 37 additions & 0 deletions tests/Jobs/UserTouAcceptanceJobTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Tests\Unit\Jobs;
Comment thread
tarrow marked this conversation as resolved.
Outdated

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{
Comment thread
tarrow marked this conversation as resolved.
Outdated
use RefreshDatabase;

public function testTouAcceptanceJob(): void{
Comment thread
tarrow marked this conversation as resolved.
Outdated
$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 UserTouAcceptanceJob([]))->handle();

$latest = TermsOfUseVersion::latest()->value;

$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));
}
}
31 changes: 31 additions & 0 deletions tests/Routes/User/UserTermsOfUseAcceptanceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Tests\Routes\User;

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 {
$email = 'test+' . uniqid('', true) . '@example.com';
$user = (new UserCreateJob($email, 'thisisapassword123', true))->handle();

$this->assertDatabaseHas('tou_acceptances', [
'user_id' => $user->id,
'tou_version' => TermsOfUseVersion::latest()->value,
]);

$rows = UserTermsOfUseAcceptance::where('user_id', $user->id)->get();
$this->assertCount(1, $rows);
$acceptance = $rows->first();

$this->assertInstanceOf(TermsOfUseVersion::class, $acceptance->tou_version);
$this->assertSame(TermsOfUseVersion::latest(), $acceptance->tou_version);
$this->assertNotNull($acceptance->tou_accepted_at);
}
}
Loading