feat: accept terms
This commit is contained in:
@@ -360,6 +360,7 @@ class AuthController extends Controller
|
|||||||
$userAttributes['email'] = strtolower($data['email']);
|
$userAttributes['email'] = strtolower($data['email']);
|
||||||
$userAttributes['password'] = Hash::make($data['password']);
|
$userAttributes['password'] = Hash::make($data['password']);
|
||||||
$userAttributes['avatar_url'] = $avatarPath;
|
$userAttributes['avatar_url'] = $avatarPath;
|
||||||
|
$userAttributes['terms_accepted_at'] = now();
|
||||||
$trialDays = max(0, (int) config('billing.trial_days', 7));
|
$trialDays = max(0, (int) config('billing.trial_days', 7));
|
||||||
|
|
||||||
if ($trialDays > 0) {
|
if ($trialDays > 0) {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class RegisterRequest extends FormRequest
|
|||||||
->symbols()
|
->symbols()
|
||||||
->uncompromised(),
|
->uncompromised(),
|
||||||
],
|
],
|
||||||
|
'termsAccepted' => ['required', 'accepted'],
|
||||||
'avatar' => ['sometimes', 'nullable', 'image', 'max:2048'],
|
'avatar' => ['sometimes', 'nullable', 'image', 'max:2048'],
|
||||||
'bio' => ['nullable', 'string'],
|
'bio' => ['nullable', 'string'],
|
||||||
'physicalActivityLevel' => ['sometimes', Rule::enum(PhysicalActivityLevel::class)],
|
'physicalActivityLevel' => ['sometimes', Rule::enum(PhysicalActivityLevel::class)],
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasLocale
|
|||||||
'pace_preference',
|
'pace_preference',
|
||||||
'sex',
|
'sex',
|
||||||
'date_of_birth',
|
'date_of_birth',
|
||||||
|
'terms_accepted_at',
|
||||||
'suspended_at',
|
'suspended_at',
|
||||||
'suspended_by',
|
'suspended_by',
|
||||||
'suspended_reason',
|
'suspended_reason',
|
||||||
@@ -95,6 +96,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasLocale
|
|||||||
'sex' => UserSex::class,
|
'sex' => UserSex::class,
|
||||||
'role' => UserRole::class,
|
'role' => UserRole::class,
|
||||||
'date_of_birth' => 'immutable_date',
|
'date_of_birth' => 'immutable_date',
|
||||||
|
'terms_accepted_at' => 'datetime',
|
||||||
'suspended_at' => 'datetime',
|
'suspended_at' => 'datetime',
|
||||||
'trial_ends_at' => 'datetime',
|
'trial_ends_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?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::table('users', function (Blueprint $table) {
|
||||||
|
$table->timestampTz('terms_accepted_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('terms_accepted_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -27,6 +27,7 @@ it('sends a verification email when a user registers', function () {
|
|||||||
'pacePreference' => PacePreference::NORMAL->value,
|
'pacePreference' => PacePreference::NORMAL->value,
|
||||||
'sex' => UserSex::WOMAN->value,
|
'sex' => UserSex::WOMAN->value,
|
||||||
'dateOfBirth' => '2003-04-24',
|
'dateOfBirth' => '2003-04-24',
|
||||||
|
'termsAccepted' => true,
|
||||||
])
|
])
|
||||||
->assertCreated()
|
->assertCreated()
|
||||||
->assertJsonPath('user.email', 'leon@example.com')
|
->assertJsonPath('user.email', 'leon@example.com')
|
||||||
@@ -45,7 +46,8 @@ it('sends a verification email when a user registers', function () {
|
|||||||
|
|
||||||
expect($user->hasVerifiedEmail())->toBeFalse()
|
expect($user->hasVerifiedEmail())->toBeFalse()
|
||||||
->and($user->trial_ends_at)->not->toBeNull()
|
->and($user->trial_ends_at)->not->toBeNull()
|
||||||
->and($user->trial_ends_at->isFuture())->toBeTrue();
|
->and($user->trial_ends_at->isFuture())->toBeTrue()
|
||||||
|
->and($user->terms_accepted_at)->not->toBeNull();
|
||||||
|
|
||||||
$this->assertDatabaseHas('users', [
|
$this->assertDatabaseHas('users', [
|
||||||
'id' => $user->id,
|
'id' => $user->id,
|
||||||
@@ -71,6 +73,7 @@ it('returns a temporary error when the verification email cannot be sent during
|
|||||||
'email' => 'leon@example.com',
|
'email' => 'leon@example.com',
|
||||||
'password' => 'Motsdfdepasse123*',
|
'password' => 'Motsdfdepasse123*',
|
||||||
'locale' => 'fr-FR',
|
'locale' => 'fr-FR',
|
||||||
|
'termsAccepted' => true,
|
||||||
])
|
])
|
||||||
->assertServiceUnavailable()
|
->assertServiceUnavailable()
|
||||||
->assertJsonPath('message', __('api.auth.verification_email_failed'));
|
->assertJsonPath('message', __('api.auth.verification_email_failed'));
|
||||||
@@ -94,6 +97,7 @@ it('registers mobile users with a bearer token', function () {
|
|||||||
'pacePreference' => PacePreference::NORMAL->value,
|
'pacePreference' => PacePreference::NORMAL->value,
|
||||||
'sex' => UserSex::MAN->value,
|
'sex' => UserSex::MAN->value,
|
||||||
'dateOfBirth' => '2003-04-24',
|
'dateOfBirth' => '2003-04-24',
|
||||||
|
'termsAccepted' => true,
|
||||||
])
|
])
|
||||||
->assertCreated()
|
->assertCreated()
|
||||||
->assertJsonPath('user.email', 'mobile.leon@example.com')
|
->assertJsonPath('user.email', 'mobile.leon@example.com')
|
||||||
@@ -111,6 +115,31 @@ it('registers mobile users with a bearer token', function () {
|
|||||||
Notification::assertSentTo($user, VerifyEmail::class);
|
Notification::assertSentTo($user, VerifyEmail::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('requires users to accept the terms during registration', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$registrationData = [
|
||||||
|
'name' => 'TermsLeon',
|
||||||
|
'email' => 'terms.leon@example.com',
|
||||||
|
'password' => 'Motsdfdepasse123*',
|
||||||
|
'termsAccepted' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->postJson('/api/auth/mobile/register', $registrationData)
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonValidationErrors('termsAccepted');
|
||||||
|
|
||||||
|
unset($registrationData['termsAccepted']);
|
||||||
|
|
||||||
|
$this->postJson('/api/auth/mobile/register', $registrationData)
|
||||||
|
->assertUnprocessable()
|
||||||
|
->assertJsonValidationErrors('termsAccepted');
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('users', [
|
||||||
|
'email' => 'terms.leon@example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('uses the custom account verification mailable', function () {
|
it('uses the custom account verification mailable', function () {
|
||||||
$user = User::factory()->unverified()->create([
|
$user = User::factory()->unverified()->create([
|
||||||
'locale' => 'fr',
|
'locale' => 'fr',
|
||||||
|
|||||||
Reference in New Issue
Block a user