add controllers, servoices
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Users;
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Models\UserRole;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class UserManagementService
|
||||
{
|
||||
public function __construct(private UserEventService $eventService)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(array $payload, ?int $actorId = null): array
|
||||
{
|
||||
$actorId = $actorId ?? (auth()->id() ?? null);
|
||||
|
||||
try {
|
||||
$user = DB::transaction(function () use ($payload, $actorId) {
|
||||
$data = $this->normalizeForCreate($payload);
|
||||
$user = User::query()->create($data);
|
||||
|
||||
UserRole::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'role_id' => (int) $payload['role_id'],
|
||||
'updated_by' => $actorId,
|
||||
]);
|
||||
|
||||
return $user;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'message' => 'Failed to create user.'];
|
||||
}
|
||||
|
||||
$this->eventService->handleNewAccountAdded($user->toArray());
|
||||
|
||||
return ['ok' => true, 'user' => $user];
|
||||
}
|
||||
|
||||
public function update(int $userId, array $payload): array
|
||||
{
|
||||
$user = User::query()->find($userId);
|
||||
if (!$user) {
|
||||
return ['ok' => false, 'message' => 'User not found.'];
|
||||
}
|
||||
|
||||
$data = $this->normalizeForUpdate($payload);
|
||||
if (empty($data)) {
|
||||
return ['ok' => false, 'message' => 'No changes requested.'];
|
||||
}
|
||||
|
||||
try {
|
||||
$user->fill($data);
|
||||
$user->save();
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'message' => 'Failed to update user.'];
|
||||
}
|
||||
|
||||
return ['ok' => true, 'user' => $user];
|
||||
}
|
||||
|
||||
public function delete(int $userId): array
|
||||
{
|
||||
$user = User::query()->find($userId);
|
||||
if (!$user) {
|
||||
return ['ok' => false, 'message' => 'User not found.'];
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($userId, $user) {
|
||||
UserRole::query()->where('user_id', $userId)->delete();
|
||||
$user->delete();
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'message' => 'Failed to delete user.'];
|
||||
}
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public function assignRole(int $userId, int $roleId, ?int $actorId = null): bool
|
||||
{
|
||||
$actorId = $actorId ?? (auth()->id() ?? null);
|
||||
|
||||
if (!User::query()->whereKey($userId)->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Role::query()->whereKey($roleId)->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UserRole::updateOrInsertRole($userId, $roleId, $actorId);
|
||||
}
|
||||
|
||||
public function cleanupUnverifiedUsers(int $olderThanMinutes = 15): int
|
||||
{
|
||||
$cutoff = now()->subMinutes(max(1, $olderThanMinutes));
|
||||
|
||||
$deleted = User::query()
|
||||
->where('is_verified', 0)
|
||||
->where('created_at', '<=', $cutoff)
|
||||
->delete();
|
||||
|
||||
Log::info(sprintf(
|
||||
'Deleted %d unverified users created before %s.',
|
||||
$deleted,
|
||||
$cutoff->format('Y-m-d H:i:s')
|
||||
));
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
public function validateRegistrationInput(array $data): array
|
||||
{
|
||||
$rules = [
|
||||
'firstname' => ['required', 'regex:/^[a-zA-Z\\s-]+$/', 'min:2', 'max:30'],
|
||||
'lastname' => ['required', 'regex:/^[a-zA-Z\\s-]+$/', 'min:2', 'max:30'],
|
||||
'gender' => ['required', 'in:Male,Female'],
|
||||
'email' => ['required', 'email', 'max:50'],
|
||||
'confirm_email' => ['required', 'same:email'],
|
||||
'cellphone' => ['required', 'regex:/^[\\d\\s\\-\\(\\)\\.]+$/', 'min:10', 'max:20'],
|
||||
'address_street' => ['nullable', 'regex:/^[a-zA-Z0-9\\s\\-]+$/', 'max:150'],
|
||||
'apt' => ['nullable', 'regex:/^[a-zA-Z0-9\\s-]+$/', 'max:10'],
|
||||
'city' => ['required', 'regex:/^[a-zA-Z\\s]+$/', 'min:2', 'max:30'],
|
||||
'state' => ['required', 'in:CT,ME,MA,NH,NY,RI,VT'],
|
||||
'zip' => ['required', 'regex:/^\\d{5}$/'],
|
||||
'accept_school_policy' => ['required'],
|
||||
];
|
||||
|
||||
$isParent = !empty($data['is_parent']);
|
||||
$noSecondParent = !empty($data['no_second_parent_info']);
|
||||
|
||||
if ($isParent && !$noSecondParent) {
|
||||
$rules += [
|
||||
'second_firstname' => ['required', 'regex:/^[a-zA-Z\\s\\-]+$/', 'min:2', 'max:30'],
|
||||
'second_lastname' => ['required', 'regex:/^[a-zA-Z\\s\\-]+$/', 'min:2', 'max:30'],
|
||||
'second_gender' => ['required', 'in:Male,Female'],
|
||||
'second_email' => ['required', 'email', 'max:50'],
|
||||
'second_cellphone' => ['required', 'regex:/^[\\d\\s\\-\\(\\)\\.]+$/', 'min:10', 'max:20'],
|
||||
];
|
||||
}
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $validator->errors()->toArray();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function normalizeForCreate(array $payload): array
|
||||
{
|
||||
return [
|
||||
'school_id' => $payload['school_id'] ?? null,
|
||||
'firstname' => $this->titleCase($payload['firstname'] ?? ''),
|
||||
'lastname' => $this->titleCase($payload['lastname'] ?? ''),
|
||||
'gender' => $this->normalizeGender($payload['gender'] ?? null),
|
||||
'cellphone' => (string) ($payload['cellphone'] ?? ''),
|
||||
'email' => strtolower((string) ($payload['email'] ?? '')),
|
||||
'address_street' => (string) ($payload['address_street'] ?? ''),
|
||||
'apt' => $payload['apt'] ?? null,
|
||||
'city' => $this->titleCase($payload['city'] ?? ''),
|
||||
'state' => (string) ($payload['state'] ?? ''),
|
||||
'zip' => (string) ($payload['zip'] ?? ''),
|
||||
'accept_school_policy' => (bool) ($payload['accept_school_policy'] ?? false),
|
||||
'is_verified' => isset($payload['is_verified']) ? (bool) $payload['is_verified'] : 0,
|
||||
'status' => $payload['status'] ?? 'Inactive',
|
||||
'is_suspended' => isset($payload['is_suspended']) ? (bool) $payload['is_suspended'] : 0,
|
||||
'failed_attempts' => $payload['failed_attempts'] ?? null,
|
||||
'password' => pbkdf2_hash((string) ($payload['password'] ?? '')),
|
||||
'token' => $payload['token'] ?? null,
|
||||
'account_id' => $payload['account_id'] ?? null,
|
||||
'user_type' => $payload['user_type'] ?? 'primary',
|
||||
'semester' => (string) ($payload['semester'] ?? ''),
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'rfid_tag' => $payload['rfid_tag'] ?? null,
|
||||
'last_failed_at' => $this->normalizeDateTime($payload['last_failed_at'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeForUpdate(array $payload): array
|
||||
{
|
||||
$data = [];
|
||||
$map = [
|
||||
'school_id',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'gender',
|
||||
'cellphone',
|
||||
'email',
|
||||
'address_street',
|
||||
'apt',
|
||||
'city',
|
||||
'state',
|
||||
'zip',
|
||||
'accept_school_policy',
|
||||
'user_type',
|
||||
'rfid_tag',
|
||||
'failed_attempts',
|
||||
'last_failed_at',
|
||||
'semester',
|
||||
'school_year',
|
||||
'status',
|
||||
'is_suspended',
|
||||
'is_verified',
|
||||
'token',
|
||||
'account_id',
|
||||
];
|
||||
|
||||
foreach ($map as $field) {
|
||||
if (!array_key_exists($field, $payload)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $payload[$field];
|
||||
switch ($field) {
|
||||
case 'firstname':
|
||||
case 'lastname':
|
||||
case 'city':
|
||||
$data[$field] = $this->titleCase($value);
|
||||
break;
|
||||
case 'email':
|
||||
$data[$field] = strtolower((string) $value);
|
||||
break;
|
||||
case 'gender':
|
||||
$data[$field] = $this->normalizeGender($value);
|
||||
break;
|
||||
case 'accept_school_policy':
|
||||
case 'is_suspended':
|
||||
case 'is_verified':
|
||||
$data[$field] = (bool) $value;
|
||||
break;
|
||||
case 'last_failed_at':
|
||||
$data[$field] = $this->normalizeDateTime($value);
|
||||
break;
|
||||
case 'failed_attempts':
|
||||
case 'school_id':
|
||||
$data[$field] = $value === '' ? null : $value;
|
||||
break;
|
||||
default:
|
||||
$data[$field] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function normalizeGender(?string $value): ?string
|
||||
{
|
||||
$value = strtolower(trim((string) $value));
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
if ($value === 'male') {
|
||||
return 'Male';
|
||||
}
|
||||
if ($value === 'female') {
|
||||
return 'Female';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function titleCase(?string $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
return ucfirst($value);
|
||||
}
|
||||
|
||||
private function normalizeDateTime($value): ?string
|
||||
{
|
||||
$value = trim((string) ($value ?? ''));
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return CarbonImmutable::parse($value)->format('Y-m-d H:i:s');
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user