fix(auth): scope email uniqueness to active users
This commit is contained in:
@@ -19,7 +19,7 @@ use Illuminate\Notifications\Notifiable;
|
|||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
#[Fillable(['nombre_apellido', 'email', 'password', 'dni', 'telefono', 'google_id', 'rol_codigo', 'tenant_codigo'])]
|
#[Fillable(['nombre_apellido', 'email', 'password', 'dni', 'telefono', 'google_id', 'rol_codigo', 'tenant_codigo'])]
|
||||||
#[Hidden(['password', 'remember_token'])]
|
#[Hidden(['password', 'remember_token', 'active_email'])]
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<UserFactory> */
|
/** @use HasFactory<UserFactory> */
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ class RegisterUserRequest extends FormRequest
|
|||||||
return [
|
return [
|
||||||
'tenant_codigo' => ['nullable', 'string', Rule::exists('tenants', 'codigo')],
|
'tenant_codigo' => ['nullable', 'string', Rule::exists('tenants', 'codigo')],
|
||||||
'nombre_apellido' => ['required', 'string', 'max:255'],
|
'nombre_apellido' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')],
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'email',
|
||||||
|
'max:255',
|
||||||
|
Rule::unique('users', 'email')->whereNull('deleted_at'),
|
||||||
|
],
|
||||||
'password' => ['required', 'string', 'confirmed', Password::min(8)->mixedCase()->symbols()],
|
'password' => ['required', 'string', 'confirmed', Password::min(8)->mixedCase()->symbols()],
|
||||||
'dni' => ['nullable', 'string', 'max:255'],
|
'dni' => ['nullable', 'string', 'max:255'],
|
||||||
'telefono' => ['nullable', 'string', 'max:255'],
|
'telefono' => ['nullable', 'string', 'max:255'],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Domains\Auth\Requests;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
class UpdateProfileRequest extends FormRequest
|
class UpdateProfileRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@@ -19,11 +20,13 @@ class UpdateProfileRequest extends FormRequest
|
|||||||
'email' => [
|
'email' => [
|
||||||
'required',
|
'required',
|
||||||
'email',
|
'email',
|
||||||
Rule::unique('users', 'email')->ignore($this->user()->id),
|
Rule::unique('users', 'email')
|
||||||
|
->whereNull('deleted_at')
|
||||||
|
->ignore($this->user()->id),
|
||||||
],
|
],
|
||||||
'dni' => ['nullable', 'string', 'regex:/^[0-9]{7,8}$/'],
|
'dni' => ['nullable', 'string', 'regex:/^[0-9]{7,8}$/'],
|
||||||
'telefono' => ['nullable', 'string', 'regex:/^\+?[0-9\s\-]+$/'],
|
'telefono' => ['nullable', 'string', 'regex:/^\+?[0-9\s\-]+$/'],
|
||||||
'password' => ['nullable', 'string', \Illuminate\Validation\Rules\Password::min(8)->mixedCase()->symbols()],
|
'password' => ['nullable', 'string', Password::min(8)->mixedCase()->symbols()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ class StoreStaffRequest extends FormRequest
|
|||||||
return [
|
return [
|
||||||
'nombre_apellido' => ['required', 'string', 'max:255'],
|
'nombre_apellido' => ['required', 'string', 'max:255'],
|
||||||
'dni' => ['required', 'string', 'max:50'],
|
'dni' => ['required', 'string', 'max:50'],
|
||||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'email',
|
||||||
|
'max:255',
|
||||||
|
Rule::unique('users', 'email')->whereNull('deleted_at'),
|
||||||
|
],
|
||||||
'category_ids' => $categoryRules,
|
'category_ids' => $categoryRules,
|
||||||
'category_ids.*' => ['integer', 'distinct', Rule::exists('categorias', 'id')],
|
'category_ids.*' => ['integer', 'distinct', Rule::exists('categorias', 'id')],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ class UpdateStaffRequest extends FormRequest
|
|||||||
'required',
|
'required',
|
||||||
'email',
|
'email',
|
||||||
'max:255',
|
'max:255',
|
||||||
Rule::unique('users', 'email')->ignore($staffId),
|
Rule::unique('users', 'email')
|
||||||
|
->whereNull('deleted_at')
|
||||||
|
->ignore($staffId),
|
||||||
],
|
],
|
||||||
'category_ids' => $categoryRules,
|
'category_ids' => $categoryRules,
|
||||||
'category_ids.*' => ['integer', 'distinct', Rule::exists('categorias', 'id')],
|
'category_ids.*' => ['integer', 'distinct', Rule::exists('categorias', 'id')],
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table): void {
|
||||||
|
$table->dropUnique(['email']);
|
||||||
|
$table->string('active_email')
|
||||||
|
->nullable()
|
||||||
|
->storedAs('CASE WHEN `deleted_at` IS NULL THEN LOWER(`email`) ELSE NULL END');
|
||||||
|
$table->unique('active_email');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table): void {
|
||||||
|
$table->dropUnique(['active_email']);
|
||||||
|
$table->dropColumn('active_email');
|
||||||
|
$table->unique('email');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -121,6 +121,11 @@ class StaffControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->deleteJson("/api/v1/adminapp/tenant/staff/{$staffId}")->assertNoContent();
|
$this->deleteJson("/api/v1/adminapp/tenant/staff/{$staffId}")->assertNoContent();
|
||||||
$this->assertSoftDeleted('users', ['id' => $staffId]);
|
$this->assertSoftDeleted('users', ['id' => $staffId]);
|
||||||
|
$this->assertDatabaseHas('users', [
|
||||||
|
'id' => $staffId,
|
||||||
|
'email' => 'ada@example.test',
|
||||||
|
'active_email' => null,
|
||||||
|
]);
|
||||||
$this->assertDatabaseMissing('personal_access_tokens', ['id' => $accessTokenId]);
|
$this->assertDatabaseMissing('personal_access_tokens', ['id' => $accessTokenId]);
|
||||||
$this->assertDatabaseHas('category_scanners', [
|
$this->assertDatabaseHas('category_scanners', [
|
||||||
'user_id' => $staffId,
|
'user_id' => $staffId,
|
||||||
@@ -133,6 +138,24 @@ class StaffControllerTest extends TestCase
|
|||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonCount(0, 'data');
|
->assertJsonCount(0, 'data');
|
||||||
|
|
||||||
|
$replacementResponse = $this->postJson('/api/v1/adminapp/tenant/staff', [
|
||||||
|
'nombre_apellido' => 'Nueva Ada',
|
||||||
|
'dni' => '11223344',
|
||||||
|
'email' => 'ADA@example.test',
|
||||||
|
'category_ids' => [$firstCategory->id],
|
||||||
|
])->assertSuccessful()
|
||||||
|
->assertJsonPath('data.email', 'ada@example.test');
|
||||||
|
|
||||||
|
$replacementStaffId = $replacementResponse->json('data.id');
|
||||||
|
$this->assertNotSame($staffId, $replacementStaffId);
|
||||||
|
$this->assertSame('Ada Byron', $ticket->fresh()->scannerUser?->nombre_apellido);
|
||||||
|
$this->assertDatabaseHas('users', [
|
||||||
|
'id' => $replacementStaffId,
|
||||||
|
'email' => 'ada@example.test',
|
||||||
|
'active_email' => 'ada@example.test',
|
||||||
|
'deleted_at' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_admin_cannot_assign_another_tenants_category(): void
|
public function test_admin_cannot_assign_another_tenants_category(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user