diff --git a/app/Jobs/UserCreateJob.php b/app/Jobs/UserCreateJob.php index e2a54f51a..9ecef4ffd 100644 --- a/app/Jobs/UserCreateJob.php +++ b/app/Jobs/UserCreateJob.php @@ -2,7 +2,9 @@ namespace App\Jobs; +use App\TermOfUseVersion; use App\User; +use App\UserTermOfUseAcceptance; use Illuminate\Support\Facades\Hash; class UserCreateJob extends Job { @@ -30,6 +32,12 @@ public function handle() { 'verified' => $this->verified, ]); + UserTermOfUseAcceptance::create([ + 'user_id' => $user->id, + 'tou_version' => TermOfUseVersion::latest(), + 'tou_accepted_at' => now(), + ]); + return $user; } } diff --git a/app/TermOfUseVersion.php b/app/TermOfUseVersion.php new file mode 100644 index 000000000..65c9d0e81 --- /dev/null +++ b/app/TermOfUseVersion.php @@ -0,0 +1,26 @@ +name, 'v')) { + continue; + } + $n = (int) substr($case->name, 1); + if ($n > $latestNum) { + $latestNum = $n; + $latestVersion = $case; + } + } + + return $latestVersion; + } +} diff --git a/app/User.php b/app/User.php index 3c15b1e38..21d121ea1 100644 --- a/app/User.php +++ b/app/User.php @@ -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(UserTermOfUseAcceptance::class, 'tou_acceptances'); + } + public function hasVerifiedEmail() { return (bool) $this->verified; } diff --git a/app/UserTermOfUseAcceptance.php b/app/UserTermOfUseAcceptance.php new file mode 100644 index 000000000..87f8d5801 --- /dev/null +++ b/app/UserTermOfUseAcceptance.php @@ -0,0 +1,27 @@ + TermOfUseVersion::class, + 'tou_accepted_at' => 'datetime', + ]; + + protected $table = 'tou_acceptances'; +} diff --git a/database/migrations/2025_09_29_194758_tou_acceptances.php b/database/migrations/2025_09_29_194758_tou_acceptances.php new file mode 100644 index 000000000..bbc1e76c8 --- /dev/null +++ b/database/migrations/2025_09_29_194758_tou_acceptances.php @@ -0,0 +1,29 @@ +id(); + $table->unsignedInteger('user_id'); + $table->string('tou_version', 10); + $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'); + } +}; diff --git a/tests/Routes/User/UserTermOfUseAcceptanceTest.php b/tests/Routes/User/UserTermOfUseAcceptanceTest.php new file mode 100644 index 000000000..cb8b85eeb --- /dev/null +++ b/tests/Routes/User/UserTermOfUseAcceptanceTest.php @@ -0,0 +1,31 @@ +handle(); + + $this->assertDatabaseHas('tou_acceptances', [ + 'user_id' => $user->id, + 'tou_version' => TermOfUseVersion::latest()->value, + ]); + + $rows = UserTermOfUseAcceptance::where('user_id', $user->id)->get(); + $this->assertCount(1, $rows); + $acceptance = $rows->first(); + + $this->assertInstanceOf(TermOfUseVersion::class, $acceptance->tou_version); + $this->assertSame(TermOfUseVersion::latest(), $acceptance->tou_version); + $this->assertNotNull($acceptance->tou_accepted_at); + } +}