Browse Source

feat: :sparkles: autenticacao aplicativos cliente e diarista

login basico via codigo + validacao de codigo e envio de email com codigo
Gustavo Zanatta 1 tháng trước cách đây
mục cha
commit
065247c741

+ 108 - 66
app/Http/Controllers/AuthController.php

@@ -4,89 +4,131 @@ namespace App\Http\Controllers;
 
 use App\Http\Requests\AuthRequest;
 use App\Http\Requests\RefreshTokenRequest;
+use App\Http\Requests\UserAppsRequest;
+use App\Http\Requests\UserAppsValidateCodeRequest;
 use Illuminate\Http\JsonResponse;
 use App\Http\Resources\AuthResource;
 use App\Services\AuthService;
 
 class AuthController extends Controller
 {
-    public function __construct(protected AuthService $authService) {}
+  public function __construct(protected AuthService $authService) {}
 
-    public function login(AuthRequest $request): JsonResponse
-    {
-        $validated = $request->validated();
+  public function login(AuthRequest $request): JsonResponse
+  {
+    $validated = $request->validated();
 
-        $result = $this->authService->login(
-            email: $validated["email"],
-            password: $validated["password"],
-        );
+    $result = $this->authService->login(
+      email: $validated["email"],
+      password: $validated["password"],
+    );
 
-        if (!$result) {
-            return $this->errorResponse(message: __("auth.failed"), code: 401);
-        }
-
-        return $this->successResponse(
-            payload: new AuthResource($result["payload"]),
-            message: __("auth.logged_in"),
-        )->withCookie(
-            cookie(
-                "refresh_token",
-                $result["refreshToken"],
-                config("sanctum.rt_expiration") * 60,
-                "/",
-                config("session.domain"),
-                config("session.secure"),
-                true,
-                false,
-                "Lax",
-            ),
-        );
+    if (!$result) {
+      return $this->errorResponse(message: __("auth.failed"), code: 401);
+    }
+
+    return $this->successResponse(
+      payload: new AuthResource($result["payload"]),
+      message: __("auth.logged_in"),
+    )->withCookie(
+      cookie(
+        "refresh_token",
+        $result["refreshToken"],
+        config("sanctum.rt_expiration") * 60,
+        "/",
+        config("session.domain"),
+        config("session.secure"),
+        true,
+        false,
+        "Lax",
+      ),
+    );
+  }
+
+  public function logout(): JsonResponse
+  {
+    $this->authService->logout();
+
+    return $this->successResponse(
+      message: __("auth.logout"),
+    )->withoutCookie("refresh_token");
+  }
+
+  public function refresh(RefreshTokenRequest $request): JsonResponse
+  {
+    $refresh_token = $request->cookie("refresh_token");
+
+    if (is_null($refresh_token)) {
+      return $this->errorResponse(
+        code: 403,
+      )->withoutCookie("refresh_token");
     }
 
-    public function logout(): JsonResponse
-    {
-        $this->authService->logout();
+    $result = $this->authService->refresh(
+      $refresh_token
+    );
 
-        return $this->successResponse(
-            message: __("auth.logout"),
-        )->withoutCookie("refresh_token");
+    if (is_null($result)) {
+      return $this->errorResponse(
+        message: __("auth.unauthorized"),
+        code: 403,
+      )->withoutCookie("refresh_token");
     }
 
-    public function refresh(RefreshTokenRequest $request): JsonResponse
-    {
-        $refresh_token = $request->cookie("refresh_token");
+    return $this->successResponse(
+      payload: new AuthResource($result["payload"]),
+    )->withCookie(
+      cookie(
+        "refresh_token",
+        $result["refreshToken"],
+        config("sanctum.rt_expiration") * 60,
+        "/",
+        config("session.domain"),
+        config("session.secure"),
+        true,
+        true,
+        "Lax",
+      ),
+    );
+  }
 
-        if (is_null($refresh_token)) {
-            return $this->errorResponse(
-                code: 403,
-            )->withoutCookie("refresh_token");
-        }
+  public function sendCode(UserAppsRequest $request): JsonResponse
+  {
+    $this->authService->sendCode($request->validated());
+    return $this->successResponse(
+      message: __("messages.code_sent"),
+      code: 201,
+    );
+  }
 
-        $result = $this->authService->refresh(
-            $refresh_token
-        );
+  public function validateCode(UserAppsValidateCodeRequest $request): JsonResponse
+  {
+    try {
+
+      $email = $request->input("email");
+      $phone = $request->input("phone");
+      $code = $request->input("code");
 
-        if (is_null($result)) {
-            return $this->errorResponse(
-                message: __("auth.unauthorized"),
-                code: 403,
-            )->withoutCookie("refresh_token");
-        }
-
-        return $this->successResponse(
-            payload: new AuthResource($result["payload"]),
-        )->withCookie(
-            cookie(
-                "refresh_token",
-                $result["refreshToken"],
-                config("sanctum.rt_expiration") * 60,
-                "/",
-                config("session.domain"),
-                config("session.secure"),
-                true,
-                true,
-                "Lax",
-            ),
+      $result = $this->authService->validateCode($request->validated());
+  
+      if (!$result) {
+        return $this->errorResponse(
+          message: __("auth.invalid_code"),
+          code: 400,
         );
+      }
+  
+      return $this->successResponse(
+        payload: ['email' => $email, 'phone' => $phone, 'code' => $code],
+        message: __("auth.valid_code"),
+        code: 200,
+      );
+    } catch (\Exception $e) {
+      return $this->errorResponse(
+        message: __("auth.validation_error"),
+        code: 500,
+      );
     }
+  }
+  
 }

+ 69 - 79
app/Http/Controllers/ClientController.php

@@ -3,92 +3,82 @@
 namespace App\Http\Controllers;
 
 use App\Http\Requests\ClientRequest;
+use App\Http\Requests\RegisterClientRequest;
 use App\Http\Resources\ClientResource;
 use App\Services\ClientService;
 use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
 use Illuminate\Http\JsonResponse;
+use App\Http\Resources\AuthResource;
+use Illuminate\Support\Facades\Log;
 
 class ClientController extends Controller
 {
-    // protected ClientService $clientService;
-
-    // public function __construct(ClientService $clientService)
-    // {
-    //     $this->clientService = $clientService;
-    // }
-
-    // public function index(): AnonymousResourceCollection
-    // {
-    //     $clients = $this->clientService->getAll();
-    //     return ClientResource::collection($clients);
-    // }
-
-    // public function store(ClientRequest $request): ClientResource
-    // {
-    //     $client = $this->clientService->create($request->validated());
-    //     return new ClientResource($client->load('user'));
-    // }
-
-    // public function show(string $id): ClientResource
-    // {
-    //     $client = $this->clientService->findById($id);
-    //     return new ClientResource($client);
-    // }
-
-    // public function update(ClientRequest $request, string $id): JsonResponse
-    // {
-    //     $this->clientService->update($request->validated(), $id);
-    //     return response()->json(['message' => 'Client updated successfully']);
-    // }
-
-    // public function destroy(string $id): JsonResponse
-    // {
-    //     $this->clientService->delete($id);
-    //     return response()->json(['message' => 'Client deleted successfully']);
-    // }
-
-
-    public function __construct(protected ClientService $service) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(
-            payload: ClientResource::collection($items),
-        );
-    }
-
-    public function store(ClientRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(
-            payload: new ClientResource($item),
-            message: __("messages.created"),
-            code: 201,
-        );
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ClientResource($item));
-    }
-
-    public function update(ClientRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($request->validated(), $id);
-        return $this->successResponse(
-            payload: new ClientResource($item),
-            message: __("messages.updated"),
-        );
+  public function __construct(protected ClientService $service) {}
+
+  public function index(): JsonResponse
+  {
+    $items = $this->service->getAll();
+    return $this->successResponse(
+      payload: ClientResource::collection($items),
+    );
+  }
+
+  public function store(ClientRequest $request): JsonResponse
+  {
+    $item = $this->service->create($request->validated());
+    return $this->successResponse(
+      payload: new ClientResource($item),
+      message: __("messages.created"),
+      code: 201,
+    );
+  }
+
+  public function show(int $id): JsonResponse
+  {
+    $item = $this->service->findById($id);
+    return $this->successResponse(payload: new ClientResource($item));
+  }
+
+  public function update(ClientRequest $request, int $id): JsonResponse
+  {
+    $item = $this->service->update($request->validated(), $id);
+    return $this->successResponse(
+      payload: new ClientResource($item),
+      message: __("messages.updated"),
+    );
+  }
+
+  public function destroy(int $id): JsonResponse
+  {
+    $this->service->delete($id);
+    return $this->successResponse(
+      message: __("messages.deleted"),
+      code: 204,
+    );
+  }
+
+  public function register(RegisterClientRequest $request): JsonResponse
+  {
+    $result = $this->service->register($request->validated());
+    if (!$result) {
+      return $this->errorResponse(message: __("auth.failed"), code: 401);
     }
 
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(
-            message: __("messages.deleted"),
-            code: 204,
-        );
-    }
+    return $this->successResponse(
+      payload: new AuthResource($result["payload"]),
+      message: __("auth.logged_in"),
+    )->withCookie(
+      cookie(
+        "refresh_token",
+        $result["refreshToken"],
+        config("sanctum.rt_expiration") * 60,
+        "/",
+        config("session.domain"),
+        config("session.secure"),
+        true,
+        false,
+        "Lax",
+      ),
+    );
+  }
 }

+ 28 - 0
app/Http/Controllers/ProviderController.php

@@ -4,8 +4,11 @@ namespace App\Http\Controllers;
 
 use App\Services\ProviderService;
 use App\Http\Requests\ProviderRequest;
+use App\Http\Requests\RegisterClientRequest;
 use App\Http\Resources\ProviderResource;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Log;
+use App\Http\Resources\AuthResource;
 
 class ProviderController extends Controller
 {
@@ -52,4 +55,29 @@ class ProviderController extends Controller
             code: 204,
         );
     }
+
+    public function register(RegisterClientRequest $request): JsonResponse
+    {
+      $result = $this->service->register($request->validated());
+      if (!$result) {
+        return $this->errorResponse(message: __("auth.failed"), code: 401);
+      }
+
+      return $this->successResponse(
+        payload: new AuthResource($result["payload"]),
+        message: __("auth.logged_in"),
+      )->withCookie(
+        cookie(
+          "refresh_token",
+          $result["refreshToken"],
+          config("sanctum.rt_expiration") * 60,
+          "/",
+          config("session.domain"),
+          config("session.secure"),
+          true,
+          false,
+          "Lax",
+        ),
+      );
+    }
 }

+ 33 - 0
app/Http/Requests/RegisterClientRequest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
+
+class RegisterClientRequest extends FormRequest
+{
+  public function rules(): array
+  {
+    $rules = [
+      'email' => 'sometimes|email',
+      'phone' => 'sometimes|string|nullable',
+      'name' => 'required|string|max:255',
+      'code' => 'required|string|max:6',
+      'document' => [
+        'sometimes',
+        'string',
+      ],
+      'zip_code' => 'sometimes|string|max:20',
+      'address' => 'sometimes|string|max:255',
+      'has_complement' => 'sometimes|boolean',
+      'nickname' => 'sometimes|string|max:255',
+      'instructions' => 'sometimes|string|max:255',
+      'address_type' => 'sometimes|string|max:255',
+      'city' => 'sometimes|string|max:255',
+      'state' => 'sometimes|string|max:255',
+    ];
+
+    return $rules;
+  }
+}

+ 34 - 0
app/Http/Requests/UserAppsRequest.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use App\Enums\{
+  UserTypeEnum,
+  LanguageEnum
+};
+use Illuminate\Validation\Rule;
+
+class UserAppsRequest extends FormRequest
+{
+  public function rules(): array
+  {
+    $rules = [
+      'email' => 'sometimes|email|unique:users,email',
+      'phone' => 'sometimes|string|nullable',
+      'type' => ['sometimes', Rule::enum(UserTypeEnum::class)],
+      'code' => 'sometimes|string|nullable',
+    ];
+
+    if (!$this->has('email')) {
+      $rules['phone'] = 'required|string|max:20';
+      $rules['email'] = 'nullable';
+    }
+    if (!$this->has('phone')) {
+      $rules['email'] = 'required|email|unique:users,email';
+      $rules['phone'] = 'nullable';
+    }
+
+    return $rules;
+  }
+}

+ 28 - 0
app/Http/Requests/UserAppsValidateCodeRequest.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use App\Enums\{
+  UserTypeEnum,
+  LanguageEnum
+};
+
+class UserAppsValidateCodeRequest extends FormRequest
+{
+  public function rules(): array
+  {
+    $rules = [
+      'code' => 'required',
+    ];
+
+    if ($this->has('email')) {
+      $rules['email'] = 'required|email';
+    }
+    if (!$this->has('phone')) {
+      $rules['phone'] = 'required|string|max:20';
+    }
+
+    return $rules;
+  }
+}

+ 38 - 0
app/Mail/SendCodeMail.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Mail\Mailables\Content;
+use Illuminate\Mail\Mailables\Envelope;
+use Illuminate\Queue\SerializesModels;
+
+class SendCodeMail extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    public function __construct(
+        public readonly string $code,
+        public readonly string $recipientName = '',
+    ) {}
+
+    public function envelope(): Envelope
+    {
+        return new Envelope(
+            subject: __('mail.send_code.subject'),
+        );
+    }
+
+    public function content(): Content
+    {
+        return new Content(
+            view: 'emails.send_code',
+        );
+    }
+
+    public function attachments(): array
+    {
+        return [];
+    }
+}

+ 24 - 0
app/Models/User.php

@@ -121,4 +121,28 @@ class User extends Authenticatable
             "permission_id",
         );
     }
+
+    /**
+     * Create a new access token for the user in the app.
+     */
+    public function createAccessTokenApp(string $deviceId): string
+    {
+        return $this->createToken(
+            name: "access_token_{$deviceId}",
+            abilities: ["access"],
+            expiresAt: Carbon::now()->addCentury(),
+        )->plainTextToken;
+    }
+
+    /**
+     * Create a new refresh token for the user in the app.
+     */
+    public function createRefreshTokenApp(string $deviceId): string
+    {
+        return $this->createToken(
+            name: "refresh_token_{$deviceId}",
+            abilities: ["refresh"],
+            expiresAt: Carbon::now()->addCentury(),
+        )->plainTextToken;
+    }
 }

+ 176 - 81
app/Services/AuthService.php

@@ -7,100 +7,195 @@ use App\Models\PersonalAccessToken;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Str;
 
 class AuthService
 {
-    public function login(string $email, string $password): ?array
-    {
-        if (!Auth::attempt(["email" => $email, "password" => $password]) || (User::where("email", $email)->first()->type == 'CLIENT' || User::where("email", $email)->first()->type == 'PROVIDER')) {
-            return null;
-        }
-
-        $user = User::where("email", $email)->first();
-        $deviceId = Str::uuid()->toString();
-
-        $accessToken = $user->createAccessToken($deviceId);
-        $refreshToken = $user->createRefreshToken($deviceId);
-
-        return [
-            "payload" => [
-                "access_token" => $accessToken,
-                "user" => $user,
-            ],
-            "refreshToken" => $refreshToken,
-        ];
+  public function __construct(
+    private readonly EmailService $emailService,
+  ) {}
+
+  public function login(string $email, string $password): ?array
+  {
+    if (!Auth::attempt(["email" => $email, "password" => $password]) || (User::where("email", $email)->first()->type == 'CLIENT' || User::where("email", $email)->first()->type == 'PROVIDER')) {
+      return null;
     }
 
-    public function refresh(string $refreshToken): ?array
-    {
-        if (!$refreshToken) {
-            return null;
-        }
-
-        $tokenModel = PersonalAccessToken::findToken($refreshToken);
-
-        if (
-            !$tokenModel ||
-            !in_array("refresh", $tokenModel->abilities) ||
-            $tokenModel->expires_at < now()
-        ) {
-            return null;
-        }
-
-        $user = $tokenModel->tokenable;
-        if (!$user) {
-            return null;
-        }
-
-        $deviceId = Str::afterLast($tokenModel->name, "_");
-
-        $tokens = $this->refreshTokenTransaction($tokenModel, $user, $deviceId);
-
-        return [
-            "payload" => [
-                "access_token" => $tokens["access_token"],
-                "user" => $user,
-            ],
-            "refreshToken" => $tokens["refresh_token"],
-        ];
+    $user = User::where("email", $email)->first();
+    $deviceId = Str::uuid()->toString();
+
+    $accessToken = $user->createAccessToken($deviceId);
+    $refreshToken = $user->createRefreshToken($deviceId);
+
+    return [
+      "payload" => [
+        "access_token" => $accessToken,
+        "user" => $user,
+      ],
+      "refreshToken" => $refreshToken,
+    ];
+  }
+
+  public function refresh(string $refreshToken): ?array
+  {
+    if (!$refreshToken) {
+      return null;
     }
 
-    public function logout(): void
-    {
-        $user = Auth::user();
-        if (!$user) {
-            return;
-        }
+    $tokenModel = PersonalAccessToken::findToken($refreshToken);
 
-        $tokenName = $user->currentAccessToken()->name;
-        $deviceId = Str::afterLast($tokenName, "_");
+    if (
+      !$tokenModel ||
+      !in_array("refresh", $tokenModel->abilities) ||
+      $tokenModel->expires_at < now()
+    ) {
+      return null;
+    }
+
+    $user = $tokenModel->tokenable;
+    if (!$user) {
+      return null;
+    }
+
+    $deviceId = Str::afterLast($tokenModel->name, "_");
+
+    $tokens = $this->refreshTokenTransaction($tokenModel, $user, $deviceId);
+
+    return [
+      "payload" => [
+        "access_token" => $tokens["access_token"],
+        "user" => $user,
+      ],
+      "refreshToken" => $tokens["refresh_token"],
+    ];
+  }
 
-        $user
-            ->tokens()
-            ->where("name", "like", "%_{$deviceId}")
-            ->delete();
+  public function logout(): void
+  {
+    $user = Auth::user();
+    if (!$user) {
+      return;
     }
 
-    protected function refreshTokenTransaction(
-        PersonalAccessToken $tokenModel,
-        User $user,
-        string $deviceId,
+    $tokenName = $user->currentAccessToken()->name;
+    $deviceId = Str::afterLast($tokenName, "_");
+
+    $user
+      ->tokens()
+      ->where("name", "like", "%_{$deviceId}")
+      ->delete();
+  }
+
+  protected function refreshTokenTransaction(
+    PersonalAccessToken $tokenModel,
+    User $user,
+    string $deviceId,
+  ): array {
+    return DB::transaction(function () use (
+      $tokenModel,
+      $user,
+      $deviceId,
     ): array {
-        return DB::transaction(function () use (
-            $tokenModel,
-            $user,
-            $deviceId,
-        ): array {
-            $tokenModel->update(["expires_at" => Carbon::now()]);
-
-            $accessToken = $user->createAccessToken($deviceId);
-            $refreshToken = $user->createRefreshToken($deviceId);
-
-            return [
-                "access_token" => $accessToken,
-                "refresh_token" => $refreshToken,
-            ];
+      $tokenModel->update(["expires_at" => Carbon::now()]);
+
+      $accessToken = $user->createAccessToken($deviceId);
+      $refreshToken = $user->createRefreshToken($deviceId);
+
+      return [
+        "access_token" => $accessToken,
+        "refresh_token" => $refreshToken,
+      ];
+    });
+  }
+
+  public function sendCode(array $data): void
+  {
+    try {
+      DB::beginTransaction();
+      $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
+  
+      $user = new User();
+      $user->fill($data);
+      $user->code = $code;
+      $user->name = $data['name'] ?? 'Usuário';
+      $user->type = $data['type'] ?? 'USER';
+      $user->save();
+  
+      if (!empty($data['email'])) {
+        $this->emailService->sendVerificationCode(
+          email: $data['email'],
+          code: $code,
+          recipientName: $data['name'] ?? '',
+        );
+      } elseif (!empty($data['phone'])) {
+        Log::info('SMS: envio de código por telefone ainda não implementado.', [
+          'phone' => $data['phone'],
+        ]);
+      }
+  
+      DB::commit();
+      return;
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error('Erro ao enviar código de verificação.', [
+        'error' => $e->getMessage(),
+        'data' => $data,
+      ]);
+      return;
+    }
+  }
+
+  public function validateCode(array $data): Bool
+  {
+    $email = $data['email'] ?? null;
+    $phone = $data['phone'] ?? null;
+    $code = $data['code'] ?? '';
+
+    $user = User::where(function ($query) use ($email, $phone) {
+        $query->when($email, function ($q) use ($email) {
+          $q->where('email', $email);
+        })
+        ->when($phone, function ($q) use ($phone) {
+          $q->where('phone', $phone);
         });
+      })
+      ->where('code', $code)
+      ->first();
+    Log::info($user);
+    if (!$user) {
+      return false;
+    }
+
+    // $user->code = null;
+    // $user->validated_code = true;
+    // $user->save();
+
+    return true;
+  }
+
+  public function loginWithEmail(string $email, string $code): ?array
+  {
+    $user = User::where('email', $email)
+      ->where('code', $code)
+      ->first();
+
+    if (!$user) {
+      return null;
     }
+    $deviceId = Str::uuid()->toString();
+    $accessToken = $user->createAccessTokenApp($deviceId);
+    $refreshToken = $user->createRefreshTokenApp($deviceId);
+    $user->validated_code = true;
+    $user->code = null;
+    $user->save();
+
+    return [
+      "payload" => [
+        "access_token" => $accessToken,
+        "user" => $user,
+      ],
+      "refreshToken" => $refreshToken,
+    ];
+  }
 }

+ 90 - 22
app/Services/ClientService.php

@@ -2,37 +2,105 @@
 
 namespace App\Services;
 
+use App\Models\Address;
+use App\Models\City;
 use App\Models\Client;
+use App\Models\State;
+use App\Models\User;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class ClientService
 {
-    public function getAll(): Collection
-    {
-        return Client::with(['user'])->get();
-    }
+  public function __construct(
+    private readonly AuthService $authService,
+  ) {}
 
-    public function findById(int $id): ?Client
-    {
-        return Client::with(['user'])->find($id);
-    }
+  public function getAll(): Collection
+  {
+    return Client::with(['user'])->get();
+  }
 
-    public function create(array $data): Client
-    {
-        return Client::create($data);
-    }
+  public function findById(int $id): ?Client
+  {
+    return Client::with(['user'])->find($id);
+  }
 
-    public function update(array $data, int $id): Client
-    {
-        $client = Client::findOrFail($id);
-        $client->update($data);
-        return $client;
-    }
+  public function create(array $data): Client
+  {
+    return Client::create($data);
+  }
+
+  public function update(array $data, int $id)
+  {
+    $client = Client::findOrFail($id);
+    $client->update($data);
+    return $client;
+  }
+
+  public function delete(int $id): bool
+  {
+    $client = Client::findOrFail($id);
+    return $client->delete();
+  }
+
+  public function register(array $data): ?array
+  {
+    try {
+      DB::beginTransaction();
+      $user = User::when($data['email'], function ($q) use ($data) {
+          $q->where('email', $data['email']);
+        })
+        ->when($data['phone'], function ($q) use ($data) {
+          $q->where('phone', $data['phone']);
+        })
+        ->where('type', 'CLIENT')
+        ->where('code', $data['code'])
+        ->where('validated_code', false)
+        ->first();
+
+      if(!$user) {
+        throw new \Exception(__('messages.user_not_found_or_code_not_validated'));
+      }
+      $user->name = $data['name'];
+      $user->save();
+
+      $client = new Client();
+      $client->user_id = $user->id;
+      $client->document = $data['document'];
+      $client->save();
+      $client->refresh();
+
+      $address = new Address();
+      $address->source = 'client';
+      $address->source_id = $client->id;
+      $address->zip_code = $data['zip_code'] ?? null;
+      $address->address = $data['address'] ?? null;
+      $address->has_complement = $data['has_complement'] ?? null;
+      $address->nickname = $data['nickname'] ?? null;
+      $address->instructions = $data['instructions'] ?? null;
+      $address->address_type = $data['address_type'] ?? null;
+
+
+      $state = State::where('code', $data['state'])->first();
+      $city = City::where('name', $data['city'])->where('state_id', $state->id)->first();
+
+      $address->state_id = $state->id;
+      $address->city_id = $city->id;
+      $address->save();
+
+      $result = $this->authService->loginWithEmail(
+        email: $user->email,
+        code: $user->code,
+      );
 
-    public function delete(int $id): bool
-    {
-        $client = Client::findOrFail($id);
-        return $client->delete();
+      DB::commit();
+      return $result;
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error("Error registering client: " . $e->getMessage());
+      throw $e;
     }
+  }
 }

+ 21 - 0
app/Services/EmailService.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Services;
+
+use App\Mail\SendCodeMail;
+use Illuminate\Support\Facades\Mail;
+
+class EmailService
+{
+    /**
+     * Envia o e-mail com o código de verificação para o usuário.
+     *
+     * @param  string  $email         Endereço de e-mail do destinatário.
+     * @param  string  $code          Código de 6 dígitos gerado.
+     * @param  string  $recipientName Nome do destinatário (opcional).
+     */
+    public function sendVerificationCode(string $email, string $code, string $recipientName = ''): void
+    {
+        Mail::to($email)->send(new SendCodeMail($code, $recipientName));
+    }
+}

+ 99 - 30
app/Services/ProviderService.php

@@ -2,51 +2,120 @@
 
 namespace App\Services;
 
+use App\Models\Address;
+use App\Models\City;
 use App\Models\Provider;
+use App\Models\State;
+use App\Models\User;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class ProviderService
 {
-    public function getAll(): Collection
-    {
-        $providers = Provider::query()
-            ->with(['user', 'profileMedia'])
-            ->orderBy("created_at", "desc")
-            ->get();
-        return $providers;
-    }
+  public function __construct(
+    private readonly AuthService $authService,
+  ) {}
 
-    public function findById(int $id): ?Provider
-    {
-        return Provider::with(['user', 'profileMedia'])->find($id);
-    }
+  public function getAll(): Collection
+  {
+    $providers = Provider::query()
+      ->with(['user', 'profileMedia'])
+      ->orderBy("created_at", "desc")
+      ->get();
+    return $providers;
+  }
+
+  public function findById(int $id): ?Provider
+  {
+    return Provider::with(['user', 'profileMedia'])->find($id);
+  }
 
-    public function create(array $data): Provider
-    {
-        return Provider::create($data);
+  public function create(array $data): Provider
+  {
+    return Provider::create($data);
+  }
+
+  public function update(int $id, array $data): ?Provider
+  {
+    $model = $this->findById($id);
+
+    if (!$model) {
+      return null;
     }
 
-    public function update(int $id, array $data): ?Provider
-    {
-        $model = $this->findById($id);
+    $model->update($data);
+    return $model->fresh(['user', 'profileMedia']);
+  }
 
-        if (!$model) {
-            return null;
-        }
+  public function delete(int $id): bool
+  {
+    $model = $this->findById($id);
 
-        $model->update($data);
-        return $model->fresh(['user', 'profileMedia']);
+    if (!$model) {
+      return false;
     }
 
-    public function delete(int $id): bool
-    {
-        $model = $this->findById($id);
+    return $model->delete();
+  }
+
+    public function register(array $data): ?array
+  {
+    try {
+      DB::beginTransaction();
+      $user = User::when($data['email'], function ($q) use ($data) {
+          $q->where('email', $data['email']);
+        })
+        ->when($data['phone'], function ($q) use ($data) {
+          $q->where('phone', $data['phone']);
+        })
+        ->where('type', 'PROVIDER')
+        ->where('code', $data['code'])
+        ->where('validated_code', false)
+        ->first();
+
+      if(!$user) {
+        throw new \Exception(__('messages.user_not_found_or_code_not_validated'));
+      }
+      $user->name = $data['name'];
+      $user->save();
+
+      $provider = new Provider();
+      $provider->user_id = $user->id;
+      $provider->rg = $data['document'];
+      $provider->document = $data['document'];
+      $provider->save();
+      $provider->refresh();
+
+      $address = new Address();
+      $address->source = 'client';
+      $address->source_id = $provider->id;
+      $address->zip_code = $data['zip_code'] ?? null;
+      $address->address = $data['address'] ?? null;
+      $address->has_complement = $data['has_complement'] ?? null;
+      $address->nickname = $data['nickname'] ?? null;
+      $address->instructions = $data['instructions'] ?? null;
+      $address->address_type = $data['address_type'] ?? null;
+
+
+      $state = State::where('code', $data['state'])->first();
+      $city = City::where('name', $data['city'])->where('state_id', $state->id)->first();
+
+      $address->state_id = $state->id;
+      $address->city_id = $city->id;
+      $address->save();
 
-        if (!$model) {
-            return false;
-        }
+      $result = $this->authService->loginWithEmail(
+        email: $user->email,
+        code: $user->code,
+      );
 
-        return $model->delete();
+      DB::commit();
+      return $result;
+    } catch (\Exception $e) {
+      DB::rollBack();
+      Log::error("Error registering client: " . $e->getMessage());
+      throw $e;
     }
+  }
 }

+ 1 - 1
database/migrations/0001_01_01_000000_create_users_table.php

@@ -17,7 +17,7 @@ return new class extends Migration
             $table->string('email')->unique();
             $table->timestamp('email_verified_at')->nullable();
             $table->string('password');
-            $table->string('type')->default('GUEST');
+            $table->string('type')->default('USER');
             $table->string('language')->default('pt');
             $table->timestamps();
         });

+ 38 - 0
database/migrations/2026_03_16_092035_alter_users_add_phone_and_change_nullable.php

@@ -0,0 +1,38 @@
+<?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->string('name')->nullable()->change();
+      $table->string('email')->nullable()->change();
+      $table->string('phone')->nullable()->after('email');
+      $table->string('code', 6)->nullable();
+      $table->boolean('validated_code')->default(false)->after('code');
+    });
+  }
+
+  /**
+   * Reverse the migrations.
+   */
+  public function down(): void
+  {
+    Schema::table('users', function (Blueprint $table) {
+      //
+      $table->dropColumn('phone');
+      $table->string('name')->nullable(false)->change();
+      $table->string('email')->nullable(false)->change();
+      $table->dropColumn('code');
+      $table->dropColumn('validated_code');
+    });
+  }
+};

+ 1 - 0
lang/en/auth.php

@@ -21,4 +21,5 @@ return [
     'already_logged_out' => 'User already logged out',
     'unauthorized' => 'Unauthorized',
     'session_expired' => 'Session expired',
+    'invalid_code' => 'Invalid code',
 ];

+ 14 - 0
lang/en/mail.php

@@ -0,0 +1,14 @@
+<?php
+
+return [
+    'send_code' => [
+        'subject'           => 'Your verification code',
+        'header_subtitle'   => 'Access verification',
+        'greeting'          => 'Hello, :name!',
+        'greeting_anonymous'=> 'Hello!',
+        'body_intro'        => 'Use the code below to access your account',
+        'expiry_notice'     => 'This code expires in 15 minutes.',
+        'ignore_notice'     => 'If you did not request this code, please ignore this email.',
+        'footer_note'       => 'This is an automated email. Please do not reply.',
+    ],
+];

+ 1 - 0
lang/en/messages.php

@@ -10,4 +10,5 @@ return [
     'imported' => 'Imported successfully',
     'import_error' => 'Error importing',
     'buyer_not_allowed' => 'Buyer not allowed',
+    'code_sent' => 'Verification code sent successfully',
 ];

+ 1 - 0
lang/es/auth.php

@@ -21,4 +21,5 @@ return [
     'already_logged_out' => 'El usuario ya ha cerrado sesión',
     'unauthorized' => 'No autorizado',
     'session_expired' => 'Sesión caducada',
+    'invalid_code' => 'Código inválido',
 ];

+ 14 - 0
lang/es/mail.php

@@ -0,0 +1,14 @@
+<?php
+
+return [
+    'send_code' => [
+        'subject'           => 'Tu código de verificación',
+        'header_subtitle'   => 'Verificación de acceso',
+        'greeting'          => '¡Hola, :name!',
+        'greeting_anonymous'=> '¡Hola!',
+        'body_intro'        => 'Usa el código a continuación para acceder a tu cuenta',
+        'expiry_notice'     => 'Este código expira en 15 minutos.',
+        'ignore_notice'     => 'Si no solicitaste este código, ignora este correo.',
+        'footer_note'       => 'Este es un correo automático. Por favor, no respondas.',
+    ],
+];

+ 1 - 0
lang/es/messages.php

@@ -10,4 +10,5 @@ return [
     'imported' => 'Importado exitosamente',
     'import_error' => 'Error al importar',
     'buyer_not_allowed' => 'Comprador no permitido',
+    'code_sent' => 'Código de verificación enviado exitosamente',
 ];

+ 1 - 0
lang/pt/auth.php

@@ -21,4 +21,5 @@ return [
     'already_logged_out' => 'Usuário já desconectado',
     'unauthorized' => 'Não autorizado',
     'session_expired' => 'Sessão expirada',
+    'invalid_code' => 'Código inválido',
 ];

+ 14 - 0
lang/pt/mail.php

@@ -0,0 +1,14 @@
+<?php
+
+return [
+    'send_code' => [
+        'subject'           => 'Seu código de verificação',
+        'header_subtitle'   => 'Verificação de acesso',
+        'greeting'          => 'Olá, :name!',
+        'greeting_anonymous'=> 'Olá!',
+        'body_intro'        => 'Use o código abaixo para acessar sua conta',
+        'expiry_notice'     => 'Este código expira em 15 minutos.',
+        'ignore_notice'     => 'Se você não solicitou este código, ignore este e-mail.',
+        'footer_note'       => 'Este é um e-mail automático. Por favor, não responda.',
+    ],
+];

+ 1 - 0
lang/pt/messages.php

@@ -10,4 +10,5 @@ return [
     'imported' => 'Importado com sucesso',
     'import_error' => 'Erro ao importar',
     'buyer_not_allowed' => 'Compra não permitida, tente outro ingresso ou comprador',
+    'code_sent' => 'Código de verificação enviado com sucesso',
 ];

+ 138 - 0
resources/views/emails/send_code.blade.php

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>{{ __('mail.send_code.subject') }}</title>
+    <style>
+        * { box-sizing: border-box; margin: 0; padding: 0; }
+
+        body {
+            background-color: #f4f4f7;
+            font-family: 'Segoe UI', Arial, sans-serif;
+            color: #333333;
+        }
+
+        .wrapper {
+            width: 100%;
+            padding: 40px 16px;
+        }
+
+        .card {
+            max-width: 520px;
+            margin: 0 auto;
+            background-color: #ffffff;
+            border-radius: 12px;
+            overflow: hidden;
+            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+        }
+
+        .header {
+            background-color: #1a6fa8;
+            padding: 32px 40px;
+            text-align: center;
+        }
+
+        .header h1 {
+            color: #ffffff;
+            font-size: 22px;
+            font-weight: 700;
+            letter-spacing: 0.5px;
+        }
+
+        .header p {
+            color: #cce3f5;
+            font-size: 13px;
+            margin-top: 4px;
+        }
+
+        .body {
+            padding: 36px 40px;
+            text-align: center;
+        }
+
+        .body p {
+            font-size: 15px;
+            line-height: 1.6;
+            color: #555555;
+        }
+
+        .code-box {
+            display: inline-block;
+            margin: 28px 0;
+            padding: 18px 40px;
+            background-color: #f0f7ff;
+            border: 2px dashed #1a6fa8;
+            border-radius: 10px;
+        }
+
+        .code-box span {
+            display: block;
+            font-size: 42px;
+            font-weight: 800;
+            letter-spacing: 10px;
+            color: #1a6fa8;
+        }
+
+        .expiry {
+            font-size: 13px;
+            color: #999999;
+            margin-top: 8px;
+        }
+
+        .footer {
+            background-color: #f9f9fb;
+            border-top: 1px solid #eeeeee;
+            padding: 24px 40px;
+            text-align: center;
+        }
+
+        .footer p {
+            font-size: 12px;
+            color: #aaaaaa;
+            line-height: 1.6;
+        }
+
+        .footer strong {
+            color: #888888;
+        }
+    </style>
+</head>
+<body>
+    <div class="wrapper">
+        <div class="card">
+
+            <div class="header">
+                <h1>Diaria App</h1>
+                <p>{{ __('mail.send_code.header_subtitle') }}</p>
+            </div>
+
+            <div class="body">
+                @if (!empty($recipientName))
+                    <p>{{ __('mail.send_code.greeting', ['name' => $recipientName]) }}</p>
+                @else
+                    <p>{{ __('mail.send_code.greeting_anonymous') }}</p>
+                @endif
+
+                <p style="margin-top: 12px;">{{ __('mail.send_code.body_intro') }}</p>
+
+                <div class="code-box">
+                    <span>{{ $code }}</span>
+                </div>
+
+                <p style="margin-top: 20px; font-size: 13px; color: #999;">
+                    {{ __('mail.send_code.ignore_notice') }}
+                </p>
+            </div>
+
+            <div class="footer">
+                <p>
+                    &copy; {{ date('Y') }} <strong>Diaria App</strong><br />
+                    {{ __('mail.send_code.footer_note') }}
+                </p>
+            </div>
+
+        </div>
+    </div>
+</body>
+</html>

+ 8 - 1
routes/noAuthRoutes/auth.php

@@ -2,7 +2,14 @@
 
 use Illuminate\Support\Facades\Route;
 use App\Http\Controllers\AuthController;
+use App\Http\Controllers\ClientController;
+use App\Http\Controllers\ProviderController;
 
 Route::post('/login', [AuthController::class, 'login']);
-
+Route::post('/user-send-code', [AuthController::class, 'sendCode']);
 Route::post('/refresh', [AuthController::class, 'refresh']);
+// user-validate-code
+Route::post('/user-validate-code', [AuthController::class, 'validateCode']);
+
+Route::post('/register-client', [ClientController::class, 'register']);
+Route::post('/register-provider', [ProviderController::class, 'register']);