Răsfoiți Sursa

feat: :sparkles: mudanças

Denis 1 an în urmă
părinte
comite
e36e656aae
79 a modificat fișierele cu 2279 adăugiri și 301 ștergeri
  1. 5 3
      .env.example
  2. 31 0
      app/Broadcasting/DTO/WebsocketEventDataDTO.php
  3. 30 0
      app/Broadcasting/Events/WebsocketEvent.php
  4. 15 0
      app/Broadcasting/Events/WebsocketEventInterface.php
  5. 64 0
      app/Commands/RefreshPermissions.php
  6. 54 0
      app/Commands/TestWebsocketEvent.php
  7. 20 0
      app/DTO/CityDTO.php
  8. 19 0
      app/DTO/CountryDTO.php
  9. 20 0
      app/DTO/StateDTO.php
  10. 13 0
      app/Enums/DefaultStatusEnum.php
  11. 1 1
      app/Enums/LanguageEnum.php
  12. 46 0
      app/Http/Controllers/CityController.php
  13. 46 0
      app/Http/Controllers/CountryController.php
  14. 46 0
      app/Http/Controllers/StateController.php
  15. 8 2
      app/Http/Controllers/UserController.php
  16. 14 12
      app/Http/Middleware/CheckPermission.php
  17. 298 0
      app/Http/Middleware/PerformanceMonitor.php
  18. 3 3
      app/Http/Middleware/SetUserLanguage.php
  19. 42 0
      app/Http/Requests/CityRequest.php
  20. 27 0
      app/Http/Requests/CountryRequest.php
  21. 44 0
      app/Http/Requests/StateRequest.php
  22. 12 12
      app/Http/Requests/UserRequest.php
  23. 35 0
      app/Http/Resources/CityResource.php
  24. 32 0
      app/Http/Resources/CountryResource.php
  25. 3 2
      app/Http/Resources/PermissionResource.php
  26. 34 0
      app/Http/Resources/StateResource.php
  27. 4 2
      app/Http/Resources/UserResource.php
  28. 4 2
      app/Http/Resources/UserTypePermissionResource.php
  29. 29 0
      app/Http/Resources/UserTypeResource.php
  30. 38 0
      app/Models/City.php
  31. 38 0
      app/Models/Country.php
  32. 7 11
      app/Models/Permission.php
  33. 2 0
      app/Models/PersonalAccessToken.php
  34. 38 0
      app/Models/State.php
  35. 4 12
      app/Models/User.php
  36. 2 9
      app/Models/UserTypePermission.php
  37. 9 0
      app/Providers/AppServiceProvider.php
  38. 48 0
      app/Repositories/CityRepository.php
  39. 20 0
      app/Repositories/CityRepositoryInterface.php
  40. 49 0
      app/Repositories/CountryRepository.php
  41. 20 0
      app/Repositories/CountryRepositoryInterface.php
  42. 49 0
      app/Repositories/StateRepository.php
  43. 20 0
      app/Repositories/StateRepositoryInterface.php
  44. 11 8
      app/Repositories/UserTypePermissionRepository.php
  45. 41 0
      app/Services/CityService.php
  46. 41 0
      app/Services/CountryService.php
  47. 41 0
      app/Services/StateService.php
  48. 6 0
      app/Services/UserService.php
  49. 74 0
      app/Traits/Base64ToFile.php
  50. 1 1
      app/Traits/EnumHelper.php
  51. 2 2
      composer.json
  52. 224 207
      composer.lock
  53. 87 0
      config/broadcasting.php
  54. 3 0
      database/migrations/2024_07_02_185050_create_personal_access_tokens_table.php
  55. 3 0
      database/migrations/2024_07_16_175714_create_permissions_and_user_type_permissions_table.php
  56. 25 0
      database/migrations/2024_12_18_184014_create_countries_table.php
  57. 27 0
      database/migrations/2024_12_18_184015_create_states_table.php
  58. 28 0
      database/migrations/2024_12_18_184016_create_cities_table.php
  59. 69 0
      database/seeders/BrazilCitiesSeeder.php
  60. 1 0
      database/seeders/DatabaseSeeder.php
  61. 19 1
      database/seeders/PermissionSeeder.php
  62. 0 2
      database/seeders/UserSeeder.php
  63. 40 0
      lang/en/http.php
  64. 5 0
      lang/en/messages.php
  65. 4 0
      lang/en/validation.php
  66. 39 0
      lang/es/http.php
  67. 5 0
      lang/es/messages.php
  68. 4 0
      lang/es/validation.php
  69. 39 0
      lang/pt/http.php
  70. 5 0
      lang/pt/messages.php
  71. 4 0
      lang/pt/validation.php
  72. 18 0
      routes/authRoutes/city.php
  73. 14 0
      routes/authRoutes/country.php
  74. 16 0
      routes/authRoutes/state.php
  75. 4 2
      routes/authRoutes/user.php
  76. 15 0
      routes/console.php
  77. 14 0
      serve.sh
  78. 2 2
      storage/stubs/Model.stub
  79. 5 5
      storage/stubs/Route.stub

+ 5 - 3
.env.example

@@ -32,9 +32,9 @@ SESSION_ENCRYPT=false
 SESSION_PATH=/
 SESSION_DOMAIN=null
 
-BROADCAST_CONNECTION=log
+BROADCAST_CONNECTION=redis
 FILESYSTEM_DISK=local
-QUEUE_CONNECTION=database
+QUEUE_CONNECTION=redis
 
 CACHE_STORE=redis
 CACHE_PREFIX=
@@ -55,10 +55,12 @@ MAIL_ENCRYPTION=null
 MAIL_FROM_ADDRESS="hello@example.com"
 MAIL_FROM_NAME="${APP_NAME}"
 
+FILESYSTEM_DRIVER=s3
+
 AWS_ACCESS_KEY_ID=
 AWS_SECRET_ACCESS_KEY=
 AWS_DEFAULT_REGION=us-east-1
 AWS_BUCKET=
+AWS_URL=https://
 AWS_USE_PATH_STYLE_ENDPOINT=false
 
-VITE_APP_NAME="${APP_NAME}"

+ 31 - 0
app/Broadcasting/DTO/WebsocketEventDataDTO.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Broadcasting\DTO;
+
+final readonly class WebsocketEventDataDTO
+{
+    public function __construct(
+        public string $room,
+        public mixed $data,
+        public string $event = 'event',
+        public ?string $projectName = null
+    ) {}
+
+    public static function from(
+        string $room,
+        mixed $data,
+        string $event = 'event'
+    ): self {
+        return new self(
+            room: $room,
+            data: $data,
+            event: $event,
+            projectName: config('app.name')
+        );
+    }
+
+    public function channelName(): string
+    {
+        return "{$this->projectName}:{$this->room}@{$this->event}";
+    }
+}

+ 30 - 0
app/Broadcasting/Events/WebsocketEvent.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Broadcasting\Events;
+
+use App\Broadcasting\Events\WebsocketEventInterface;
+use App\Broadcasting\DTO\WebsocketEventDataDTO;
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+final class WebsocketEvent implements WebsocketEventInterface, ShouldBroadcastNow
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public function __construct(
+        private readonly WebsocketEventDataDTO $dto
+    ) {}
+
+    public function broadcastOn(): Channel
+    {
+        return new Channel($this->dto->channelName());
+    }
+
+    public function broadcastWith(): array
+    {
+        return ['data' => $this->dto->data];
+    }
+}

+ 15 - 0
app/Broadcasting/Events/WebsocketEventInterface.php

@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Broadcasting\Events;
+
+use App\Broadcasting\DTO\WebsocketEventDataDTO;
+use Illuminate\Broadcasting\Channel;
+
+interface WebsocketEventInterface
+{
+    public function __construct(WebsocketEventDataDTO $dto);
+    public function broadcastOn(): Channel;
+    public function broadcastWith(): array;
+}

+ 64 - 0
app/Commands/RefreshPermissions.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Commands;
+
+use App\Models\Permission;
+use App\Models\UserTypePermission;
+use Illuminate\Console\Command;
+use Database\Seeders\PermissionSeeder;
+use Database\Seeders\UserTypePermissionSeeder;
+
+class RefreshPermissions extends Command
+{
+
+    public function __construct(
+        protected PermissionSeeder $permissionSeeder,
+        protected UserTypePermissionSeeder $userTypePermissionSeeder,
+    ) {
+        parent::__construct();
+    }
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'permissions:refresh';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Refresh all permissions and user type permissions';
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->info('Starting permissions refresh...');
+
+        try {
+            // Run the Permission seeder
+            $this->info('Running Permission seeder...');
+            Permission::truncate();
+            $this->permissionSeeder->run();
+            $this->info('Permission seeder completed successfully.');
+
+            // Run the UserTypePermission seeder
+            $this->info('Running UserTypePermission seeder...');
+            UserTypePermission::truncate();
+            $this->userTypePermissionSeeder->run();
+            $this->info('UserTypePermission seeder completed successfully.');
+
+            $this->info('Permissions refresh completed successfully!');
+            return Command::SUCCESS;
+        } catch (\Exception $e) {
+            $this->error('An error occurred while refreshing permissions:');
+            $this->error($e->getMessage());
+            return Command::FAILURE;
+        }
+    }
+}

+ 54 - 0
app/Commands/TestWebsocketEvent.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Commands;
+
+use App\Broadcasting\DTO\WebsocketEventDataDTO;
+use App\Broadcasting\Events\WebsocketEvent;
+use Illuminate\Console\Command;
+
+final class TestWebsocketEvent extends Command
+{
+    protected $signature = 'websocket:test
+                            {room}
+                            {--event=test-event}
+                            {--data=}';
+
+    protected $description = 'Test websocket broadcasting by emitting an event';
+
+    public function handle(): int
+    {
+        try {
+            $room = $this->argument('room');
+            $event = $this->option('event');
+            $data = $this->option('data')
+                ? json_decode($this->option('data'), true, 512, JSON_THROW_ON_ERROR)
+                : ['message' => 'Test message'];
+
+            $eventData = WebsocketEventDataDTO::from(
+                room: $room,
+                data: $data,
+                event: $event
+            );
+
+            event(new WebsocketEvent($eventData));
+
+            $this->info("Event broadcasted successfully");
+            $this->table(
+                ['Component', 'Value'],
+                [
+                    ['Room', $room],
+                    ['Event', $event],
+                    ['Data', json_encode($data, JSON_PRETTY_PRINT)],
+                ]
+            );
+
+            return self::SUCCESS;
+        } catch (\JsonException $e) {
+            $this->error('Invalid JSON data provided');
+            return self::FAILURE;
+        } catch (\Throwable $e) {
+            $this->error("Error broadcasting event: {$e->getMessage()}");
+            return self::FAILURE;
+        }
+    }
+}

+ 20 - 0
app/DTO/CityDTO.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\DTO;
+
+use App\Http\Requests\CityRequest;
+
+readonly class CityDTO extends BaseDTO
+{
+    public function __construct(
+        public ?string $name = null,
+        public ?int $country_id = null,
+        public ?int $state_id = null,
+        public ?string $status = null,
+    ) {}
+
+    public static function fromRequest(CityRequest $request): self
+    {
+        return new self(...$request->validated());
+    }
+}

+ 19 - 0
app/DTO/CountryDTO.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\DTO;
+
+use App\Http\Requests\CountryRequest;
+
+readonly class CountryDTO extends BaseDTO
+{
+    public function __construct(
+        public ?string $name = null,
+        public ?string $code = null,
+        public ?string $status = null,
+    ) {}
+
+    public static function fromRequest(CountryRequest $request): self
+    {
+        return new self(...$request->validated());
+    }
+}

+ 20 - 0
app/DTO/StateDTO.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\DTO;
+
+use App\Http\Requests\StateRequest;
+
+readonly class StateDTO extends BaseDTO
+{
+    public function __construct(
+        public ?string $name = null,
+        public ?string $code = null,
+        public ?int $country_id = null,
+        public ?string $status = null,
+    ) {}
+
+    public static function fromRequest(StateRequest $request): self
+    {
+        return new self(...$request->validated());
+    }
+}

+ 13 - 0
app/Enums/DefaultStatusEnum.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Enums;
+
+use App\Traits\EnumHelper;
+
+enum DefaultStatusEnum: string
+{
+    use EnumHelper;
+
+    case ACTIVE = 'ACTIVE';
+    case INACTIVE = 'INACTIVE';
+}

+ 1 - 1
app/Enums/UserLanguageEnum.php → app/Enums/LanguageEnum.php

@@ -4,7 +4,7 @@
 
 use App\Traits\EnumHelper;
 
-enum UserLanguageEnum: string
+enum LanguageEnum: string
 {
     use EnumHelper;
 

+ 46 - 0
app/Http/Controllers/CityController.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\CityService;
+use App\Http\Requests\CityRequest;
+use App\Http\Resources\CityResource;
+use App\DTO\CityDTO;
+use Illuminate\Http\JsonResponse;
+
+class CityController extends Controller
+{
+    public function __construct(
+        protected CityService $CityService,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->CityService->getAllItems();
+        return $this->successResponse(payload: CityResource::collection($items));
+    }
+
+    public function store(CityRequest $request): JsonResponse
+    {
+        $item = $this->CityService->createItem(CityDTO::fromRequest($request));
+        return $this->successResponse(payload: new CityResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->CityService->getItem($id);
+        return $this->successResponse(payload: new CityResource($item));
+    }
+
+    public function update(CityRequest $request, int $id): JsonResponse
+    {
+        $item = $this->CityService->updateItem(CityDTO::fromRequest($request), $id);
+        return $this->successResponse(payload: new CityResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->CityService->deleteItem($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 46 - 0
app/Http/Controllers/CountryController.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\CountryService;
+use App\Http\Requests\CountryRequest;
+use App\Http\Resources\CountryResource;
+use App\DTO\CountryDTO;
+use Illuminate\Http\JsonResponse;
+
+class CountryController extends Controller
+{
+    public function __construct(
+        protected CountryService $CountryService,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->CountryService->getAllItems();
+        return $this->successResponse(payload: CountryResource::collection($items));
+    }
+
+    public function store(CountryRequest $request): JsonResponse
+    {
+        $item = $this->CountryService->createItem(CountryDTO::fromRequest($request));
+        return $this->successResponse(payload: new CountryResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->CountryService->getItem($id);
+        return $this->successResponse(payload: new CountryResource($item));
+    }
+
+    public function update(CountryRequest $request, int $id): JsonResponse
+    {
+        $item = $this->CountryService->updateItem(CountryDTO::fromRequest($request), $id);
+        return $this->successResponse(payload: new CountryResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->CountryService->deleteItem($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 46 - 0
app/Http/Controllers/StateController.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\StateService;
+use App\Http\Requests\StateRequest;
+use App\Http\Resources\StateResource;
+use App\DTO\StateDTO;
+use Illuminate\Http\JsonResponse;
+
+class StateController extends Controller
+{
+    public function __construct(
+        protected StateService $StateService,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->StateService->getAllItems();
+        return $this->successResponse(payload: StateResource::collection($items));
+    }
+
+    public function store(StateRequest $request): JsonResponse
+    {
+        $item = $this->StateService->createItem(StateDTO::fromRequest($request));
+        return $this->successResponse(payload: new StateResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->StateService->getItem($id);
+        return $this->successResponse(payload: new StateResource($item));
+    }
+
+    public function update(StateRequest $request, int $id): JsonResponse
+    {
+        $item = $this->StateService->updateItem(StateDTO::fromRequest($request), $id);
+        return $this->successResponse(payload: new StateResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->StateService->deleteItem($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 8 - 2
app/Http/Controllers/UserController.php

@@ -6,6 +6,7 @@
 use App\Services\UserService;
 use App\DTO\UserDTO;
 use App\Http\Requests\UserRequest;
+use App\Http\Resources\UserTypeResource;
 use Illuminate\Http\JsonResponse;
 
 class UserController extends Controller
@@ -13,8 +14,7 @@ class UserController extends Controller
 
     public function __construct(
         protected UserService $userService,
-    ) {
-    }
+    ) {}
 
     public function me(): JsonResponse
     {
@@ -57,4 +57,10 @@ public function updateLanguage(UserRequest $request, int $id): JsonResponse
         $user = $this->userService->updateLanguage(UserDTO::fromRequest($request), $id);
         return $this->successResponse(payload: new UserResource($user), message: __('messages.updated'));
     }
+
+    public function getUserTypes(): JsonResponse
+    {
+        $user_types = $this->userService->getUserTypes();
+        return $this->successResponse(payload: new UserTypeResource($user_types));
+    }
 }

+ 14 - 12
app/Http/Middleware/CheckPermission.php

@@ -7,6 +7,7 @@
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use App\Services\UserTypePermissionService;
+use Illuminate\Support\Facades\Log;
 
 class CheckPermission
 {
@@ -26,14 +27,13 @@ public function __construct(
     public function handle(Request $request, Closure $next, string $scopes, string $permissionType)
     {
         $user = Auth::user();
-        // Get permissions for the user or guest
-        if (!$user) {
-            $userPermissions = UserTypePermissionResource::collection(resource: $this->userTypePermissionService->allGuestPermissions());
-        } else {
-            $userPermissions = UserTypePermissionResource::collection(resource: $this->userTypePermissionService->allPermissionsByUserType(userType: $user->type));
-        }
 
-        // Check the required permission for each scope
+        $userPermissions = [];
+
+        $userPermissions = $user
+            ? $this->userTypePermissionService->allPermissionsByUserType($user->type)
+            : $this->userTypePermissionService->allGuestPermissions();
+
         $hasPermission = false;
         foreach (explode(separator: '|', string: $scopes) as $scope) {
             if ($this->hasPermission(userPermissions: $userPermissions, scope: $scope, permissionType: $permissionType)) {
@@ -65,12 +65,14 @@ private function hasPermission($userPermissions, string $scope, string $permissi
 
         $requiredPermission = $bitwisePermissionTable[$permissionType] ?? 0;
 
-        foreach ($userPermissions as $permission) {
-            if ($permission['scope'] === $scope && ($permission['bits'] & $requiredPermission)) {
-                return true;
-            }
+        $permissionRecord = $userPermissions->first(function ($permission) use ($scope) {
+            return $permission->permission->scope === $scope;
+        });
+
+        if (!$permissionRecord) {
+            return false;
         }
 
-        return false;
+        return ($permissionRecord->bits & $requiredPermission) === $requiredPermission;
     }
 }

+ 298 - 0
app/Http/Middleware/PerformanceMonitor.php

@@ -0,0 +1,298 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Symfony\Component\HttpFoundation\Response;
+
+class PerformanceMonitor
+{
+    /**
+     * Initial performance markers
+     */
+    private float $startTime;
+    private int $startMemory;
+    private int $startPeakMemory;
+
+    /**
+     * Output buffer
+     */
+    private string $buffer = '';
+
+    /**
+     * ANSI color codes for console output
+     */
+    private const COLORS = [
+        'green' => "\033[32m",
+        'yellow' => "\033[33m",
+        'magenta' => "\033[35m",
+        'cyan' => "\033[36m",
+        'gray' => "\033[90m",
+        'reset' => "\033[0m"
+    ];
+
+    /**
+     * Performance thresholds (in milliseconds)
+     */
+    private const SLOW_REQUEST_THRESHOLD = 1000;
+    private const SLOW_QUERY_THRESHOLD = 100;
+
+    public function __construct()
+    {
+        $this->startTime = defined('LARAVEL_START') ? LARAVEL_START : microtime(true);
+        $this->startMemory = memory_get_usage();
+        $this->startPeakMemory = memory_get_peak_usage();
+    }
+
+    public function handle(Request $request, Closure $next): Response
+    {
+        if (app()->environment('local', 'development')) {
+            DB::enableQueryLog();
+        }
+
+        $response = $next($request);
+
+        if (app()->environment('local', 'development')) {
+            $this->logPerformanceMetrics($request);
+            $this->flushBuffer();
+        }
+
+        return $response;
+    }
+
+    private function gatherMetrics(): array
+    {
+        $memoryLimit = $this->getMemoryLimit();
+
+        return [
+            'time' => $this->getExecutionTime(),
+            'memory' => $this->getMemoryUsage(),
+            'peakMemory' => $this->getPeakMemoryUsage(),
+            'memoryLimit' => $memoryLimit,
+            'memoryPercentage' => $this->calculateMemoryPercentage($memoryLimit),
+            'queries' => $this->getQueryMetrics(),
+            'cpu' => $this->getCpuMetrics(),
+            'cache' => $this->getCacheMetrics(),
+        ];
+    }
+
+    private function getExecutionTime(): float
+    {
+        return (microtime(true) - $this->startTime) * 1000;
+    }
+
+    private function getMemoryUsage(): float
+    {
+        return (memory_get_usage() - $this->startMemory) / 1024 / 1024;
+    }
+
+    private function getPeakMemoryUsage(): float
+    {
+        return (memory_get_peak_usage() - $this->startPeakMemory) / 1024 / 1024;
+    }
+
+    private function getMemoryLimit(): int
+    {
+        $limit = ini_get('memory_limit');
+        if (!$limit) {
+            return 0;
+        }
+
+        preg_match('/^(\d+)(K|M|G)?$/i', $limit, $matches);
+        $value = (int)($matches[1] ?? 0);
+        $unit = strtoupper($matches[2] ?? '');
+
+        return match ($unit) {
+            'G' => $value * 1024 * 1024 * 1024,
+            'M' => $value * 1024 * 1024,
+            'K' => $value * 1024,
+            default => $value,
+        };
+    }
+
+    private function calculateMemoryPercentage(int $limit): float
+    {
+        if ($limit === 0) {
+            return 0.0;
+        }
+
+        return (memory_get_usage(true) / $limit) * 100;
+    }
+
+    private function getQueryMetrics(): ?array
+    {
+        $queries = DB::getQueryLog();
+        if (empty($queries)) {
+            return null;
+        }
+
+        $totalTime = 0;
+        $formattedQueries = [];
+        foreach ($queries as $query) {
+            $totalTime += $query['time'];
+            if ($query['time'] > self::SLOW_QUERY_THRESHOLD) {
+                $formattedQueries[] = [
+                    'sql' => $this->formatSql($query['query'], $query['bindings']),
+                    'time' => $query['time'],
+                ];
+            }
+        }
+
+        return [
+            'count' => count($queries),
+            'time' => $totalTime,
+            'slow_queries' => $formattedQueries,
+        ];
+    }
+
+    private function formatSql(string $sql, array $bindings): string
+    {
+        return vsprintf(str_replace(['%', '?'], ['%%', "'%s'"], $sql), $bindings);
+    }
+
+    private function getCpuMetrics(): ?array
+    {
+        if (!function_exists('getrusage')) {
+            return null;
+        }
+
+        $usage = getrusage();
+        return [
+            'user' => ($usage['ru_utime.tv_sec'] * 1000 + intval($usage['ru_utime.tv_usec'] / 1000)),
+            'system' => ($usage['ru_stime.tv_sec'] * 1000 + intval($usage['ru_stime.tv_usec'] / 1000)),
+        ];
+    }
+
+    private function getCacheMetrics(): ?array
+    {
+        if (!function_exists('opcache_get_status')) {
+            return null;
+        }
+
+        $status = opcache_get_status(false);
+        if (!$status) {
+            return null;
+        }
+
+        $stats = $status['opcache_statistics'];
+        $hits = $stats['hits'];
+        $misses = $stats['misses'];
+        $total = $hits + $misses;
+
+        return [
+            'hit_rate' => $total > 0 ? ($hits / $total * 100) : 0,
+            'memory_usage' => $status['memory_usage'],
+        ];
+    }
+
+    private function appendToBuffer(string $text): void
+    {
+        $this->buffer .= $text;
+    }
+
+    private function flushBuffer(): void
+    {
+        if (empty($this->buffer)) {
+            return;
+        }
+
+        try {
+            $written = file_put_contents('php://stderr', $this->buffer);
+            if ($written === false) {
+                error_log('Failed to write performance metrics to stderr');
+            }
+        } catch (\Throwable $e) {
+            error_log("Error writing performance metrics: {$e->getMessage()}");
+        } finally {
+            $this->buffer = '';
+        }
+    }
+
+    private function colorize(string $text, string $color): string
+    {
+        return self::COLORS[$color] . $text . self::COLORS['reset'];
+    }
+
+    private function logPerformanceMetrics(Request $request): void
+    {
+        $metrics = $this->gatherMetrics();
+
+        // Request information
+        $this->appendToBuffer(sprintf(
+            "\n%s %s %s[%s]%s\n",
+            $this->colorize($request->method(), 'green'),
+            $this->colorize($request->getRequestUri(), 'green'),
+            self::COLORS['gray'],
+            date('Y-m-d H:i:s'),
+            self::COLORS['reset']
+        ));
+
+        // Core metrics
+        $this->appendToBuffer(sprintf(
+            "➜ Time: %s%.2fms%s | Memory: %s%.2fMB%s (%.1f%%) | Peak: %s%.2fMB%s",
+            self::COLORS['yellow'],
+            $metrics['time'],
+            self::COLORS['reset'],
+            self::COLORS['magenta'],
+            $metrics['memory'],
+            self::COLORS['reset'],
+            $metrics['memoryPercentage'],
+            self::COLORS['magenta'],
+            $metrics['peakMemory'],
+            self::COLORS['reset']
+        ));
+
+        // Query metrics
+        if ($metrics['queries']) {
+            $this->appendToBuffer(sprintf(
+                " | Queries: %s%d%s (%s%.2fms%s)",
+                self::COLORS['cyan'],
+                $metrics['queries']['count'],
+                self::COLORS['reset'],
+                self::COLORS['cyan'],
+                $metrics['queries']['time'],
+                self::COLORS['reset']
+            ));
+        }
+
+        // CPU metrics
+        if ($metrics['cpu']) {
+            $totalCpu = $metrics['cpu']['user'] + $metrics['cpu']['system'];
+            $this->appendToBuffer(sprintf(
+                " | CPU: %s%.2fms%s",
+                self::COLORS['cyan'],
+                $totalCpu,
+                self::COLORS['reset']
+            ));
+        }
+
+        // Cache metrics
+        if ($metrics['cache']) {
+            $this->appendToBuffer(sprintf(
+                " | OPcache: %s%.1f%%%s",
+                self::COLORS['cyan'],
+                $metrics['cache']['hit_rate'],
+                self::COLORS['reset']
+            ));
+        }
+
+        $this->appendToBuffer("\n");
+
+        // Log slow queries
+        if ($metrics['time'] > self::SLOW_REQUEST_THRESHOLD && !empty($metrics['queries']['slow_queries'])) {
+            $this->appendToBuffer("\n" . $this->colorize("Slow Queries Detected:", 'yellow') . "\n");
+            foreach ($metrics['queries']['slow_queries'] as $query) {
+                $this->appendToBuffer(sprintf(
+                    "➜ %s%.2fms%s: %s\n",
+                    self::COLORS['yellow'],
+                    $query['time'],
+                    self::COLORS['reset'],
+                    $query['sql']
+                ));
+            }
+        }
+    }
+}

+ 3 - 3
app/Http/Middleware/SetUserLanguage.php

@@ -6,7 +6,7 @@
 use Illuminate\Http\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Illuminate\Support\Facades\App;
-use App\Enums\UserLanguageEnum;
+use App\Enums\LanguageEnum;
 
 class SetUserLanguage
 {
@@ -18,11 +18,11 @@ class SetUserLanguage
     public function handle(Request $request, Closure $next): Response
     {
         $language = substr($request->header('Accept-Language'), 0, 2);
-        $language = UserLanguageEnum::fromString($language);
+        $language = LanguageEnum::fromString($language);
         if ($language) {
             App::setLocale($language->value);
         } else {
-            App::setLocale(UserLanguageEnum::PORTUGUESE->value);
+            App::setLocale(LanguageEnum::PORTUGUESE->value);
         }
         return $next($request);
     }

+ 42 - 0
app/Http/Requests/CityRequest.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use App\Enums\DefaultStatusEnum;
+use Illuminate\Validation\Rule;
+
+class CityRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        $rules = [
+            'name' => 'sometimes|string|max:255',
+            'country_id' => 'sometimes|exists:countries,id',
+            'state_id' => [
+                'sometimes',
+                'exists:states,id',
+            ],
+            'status' => ['sometimes', Rule::enum(DefaultStatusEnum::class)],
+        ];
+
+        if ($this->isMethod('post')) {
+            $rules['name'] = [
+                'required',
+                'string',
+                'max:255',
+                Rule::unique('cities')->where(function ($query) {
+                    return $query->where('state_id', $this->state_id);
+                })
+            ];
+            $rules['country_id'] = 'required|exists:countries,id';
+            $rules['state_id'] = [
+                'required',
+                'exists:states,id',
+            ];
+            $rules['status'] = ['required', Rule::enum(DefaultStatusEnum::class)];
+        }
+
+        return $rules;
+    }
+}

+ 27 - 0
app/Http/Requests/CountryRequest.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use App\Enums\DefaultStatusEnum;
+use Illuminate\Validation\Rule;
+
+class CountryRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        $rules = [
+            'name' => 'sometimes|string|max:255|unique:countries,name',
+            'code' => 'sometimes|string|min:2|max:3|unique:countries,code',
+            'status' => ['sometimes', Rule::enum(DefaultStatusEnum::class)],
+        ];
+
+        if ($this->isMethod('post')) {
+            $rules['name'] = 'required|string|max:255|unique:countries,name';
+            $rules['code'] = 'required|string|min:2|max:3|unique:countries,code';
+            $rules['status'] = ['required', Rule::enum(DefaultStatusEnum::class)];
+        }
+
+        return $rules;
+    }
+}

+ 44 - 0
app/Http/Requests/StateRequest.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use App\Enums\DefaultStatusEnum;
+use Illuminate\Validation\Rule;
+
+class StateRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        $rules = [
+            'name' => 'sometimes|string|max:255',
+            'code' => 'sometimes|string|size:2',
+            'country_id' => 'sometimes|exists:countries,id',
+            'status' => ['sometimes', Rule::enum(DefaultStatusEnum::class)],
+        ];
+
+        if ($this->isMethod('post')) {
+            $rules['name'] = [
+                'required',
+                'string',
+                'max:255',
+                Rule::unique('states')->where(function ($query) {
+                    return $query->where('country_id', $this->country_id);
+                })
+            ];
+            $rules['code'] = [
+                'required',
+                'string',
+                'size:2',
+                Rule::unique('states')->where(function ($query) {
+                    return $query->where('country_id', $this->country_id);
+                })
+            ];
+            $rules['country_id'] = 'required|exists:countries,id';
+            $rules['status'] = ['required', Rule::enum(DefaultStatusEnum::class)];
+        }
+
+        return $rules;
+    }
+}
+

+ 12 - 12
app/Http/Requests/UserRequest.php

@@ -3,7 +3,10 @@
 namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
-use App\Enums\UserTypeEnum;
+use App\Enums\{
+    UserTypeEnum,
+    LanguageEnum
+};
 use Illuminate\Validation\Rule;
 
 class UserRequest extends FormRequest
@@ -11,26 +14,23 @@ class UserRequest extends FormRequest
     public function rules(): array
     {
         $rules = [
+            'avatar' => 'sometimes|string|nullable',
             'name' => 'sometimes|string|nullable',
+            'email' => 'sometimes|email|unique:users,email,' . $this->user?->id,
             'password' => 'sometimes|string|nullable',
             'type' => ['sometimes', Rule::enum(UserTypeEnum::class)],
-            'email' => 'sometimes|unique:users,email|email',
-            'language' => 'sometimes|string|nullable',
+            'language' => ['sometimes', Rule::enum(LanguageEnum::class)],
         ];
 
         if ($this->isMethod('post')) {
-
-            $rules['name'] = 'required|string';
-            $rules['password'] = 'required|string';
-            $rules['email'] = 'required|unique:users,email|email';
-            $rules['language'] = 'sometimes|string|in:pt,en,es';
-
+            $rules['name'] = 'required|string|max:255';
+            $rules['email'] = 'required|email|unique:users,email';
+            $rules['password'] = 'required|string|min:6';
             if (!$this->has('language')) {
-                $this->merge(['language' => 'pt']);
-            };
+                $this->merge(['language' => LanguageEnum::PORTUGUESE->value]);
+            }
         }
 
-
         return $rules;
     }
 }

+ 35 - 0
app/Http/Resources/CityResource.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+
+class CityResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'state_id' => $this->state_id,
+            'country_id' => $this->country_id,
+            'state' => $this->state,
+            'country' => new CountryResource($this->country),
+            'status' => $this->status,
+            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
+        ];
+    }
+
+    /**
+     * @param \Illuminate\Database\Eloquent\Collection<City> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CityResource>
+     */
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 32 - 0
app/Http/Resources/CountryResource.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+
+class CountryResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'code' => $this->code,
+            'status' => $this->status,
+            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
+        ];
+    }
+
+    /**
+     * @param \Illuminate\Database\Eloquent\Collection<Country> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<CountryResource>
+     */
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 3 - 2
app/Http/Resources/PermissionResource.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Resources;
 
+use Carbon\Carbon;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
@@ -21,8 +22,8 @@ public function toArray(Request $request): array
             'description' => $this->description,
             'bits' => $this->bits,
             'parent_id' => $this->parent_id,
-            'created_at' => $this->created_at,
-            'updated_at' => $this->updated_at,
+            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
         ];
     }
     public static function collection($resource): AnonymousResourceCollection

+ 34 - 0
app/Http/Resources/StateResource.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
+
+class StateResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'code' => $this->code,
+            'country_id' => $this->country_id,
+            'country' => new CountryResource($this->country),
+            'status' => $this->status,
+            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
+        ];
+    }
+
+    /**
+     * @param \Illuminate\Database\Eloquent\Collection<State> $resource
+     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection<StateResource>
+     */
+    public static function collection($resource): AnonymousResourceCollection
+    {
+        return parent::collection($resource);
+    }
+}

+ 4 - 2
app/Http/Resources/UserResource.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Resources;
 
+use Carbon\Carbon;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
@@ -20,8 +21,9 @@ public function toArray(Request $request): array
             'name' => $this->name,
             'email' => $this->email,
             'language' => $this->language,
-            'created_at' => $this->created_at,
-            'updated_at' => $this->updated_at,
+            'type' => $this->type,
+            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
         ];
     }
 

+ 4 - 2
app/Http/Resources/UserTypePermissionResource.php

@@ -2,9 +2,11 @@
 
 namespace App\Http\Resources;
 
+use Carbon\Carbon;
 use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Support\Facades\Log;
 
 class UserTypePermissionResource extends JsonResource
 {
@@ -22,8 +24,8 @@ public function toArray(Request $request): array
             'description' => $this->permission->description,
             'parent_id' => $this->permission->parent_id,
             'bits' => $this->bits,
-            'created_at' => $this->created_at,
-            'updated_at' => $this->updated_at,
+            'created_at' => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at' => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
         ];
     }
 

+ 29 - 0
app/Http/Resources/UserTypeResource.php

@@ -0,0 +1,29 @@
+<?php
+
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class UserTypeResource extends JsonResource
+{
+
+    public function toArray(Request $request): array
+    {
+        //TODO: Maybe refactor this in the future
+
+        // The resource will return this
+        // [
+        //     'value' => 'value'
+        //     ...
+        // ]
+
+        $return = [];
+        foreach ($this->resource as $value) {
+            $return[$value] = $value;
+        }
+
+        return $return;
+    }
+}

+ 38 - 0
app/Models/City.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Models;
+
+use App\Enums\DefaultStatusEnum;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class City extends Model
+{
+    use HasFactory;
+
+    protected $table = 'cities';
+
+    protected $guarded = ['id'];
+
+        /**
+     * Get the attributes that should be cast.
+     *
+     * @return array<string, string>
+     */
+    protected function casts(): array
+    {
+        return [
+            'status' => DefaultStatusEnum::class,
+        ];
+    }
+
+    public function country()
+    {
+        return $this->belongsTo(Country::class, 'country_id');
+    }
+
+    public function state()
+    {
+        return $this->belongsTo(State::class, 'state_id');
+    }
+}

+ 38 - 0
app/Models/Country.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Models;
+
+use App\Enums\DefaultStatusEnum;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Country extends Model
+{
+    use HasFactory;
+
+    protected $table = 'countries';
+
+    protected $guarded = ['id'];
+
+    /**
+     * Get the attributes that should be cast.
+     *
+     * @return array<string, string>
+     */
+    protected function casts(): array
+    {
+        return [
+            'status' => DefaultStatusEnum::class,
+        ];
+    }
+
+    public function states()
+    {
+        return $this->hasMany(State::class, 'country_id');
+    }
+
+    public function cities()
+    {
+        return $this->hasMany(City::class, 'country_id');
+    }
+}

+ 7 - 11
app/Models/Permission.php

@@ -13,17 +13,7 @@ class Permission extends Model
 {
     use HasFactory, SoftDeletes, NodeTrait;
 
-    /**
-     * The attributes that are mass assignable.
-     *
-     * @var array<int, string>
-     */
-    protected $fillable = [
-        'name',
-        'bits',
-        'description',
-        'parent_id',
-    ];
+    protected $guarded = ['id'];
 
     /**
      * The attributes that should be cast.
@@ -34,6 +24,7 @@ protected function casts(): array
     {
         return [
             'parent_id' => 'integer',
+            'bits' => 'integer',
         ];
     }
 
@@ -46,4 +37,9 @@ public function parent(): BelongsTo
     {
         return $this->belongsTo(Permission::class, 'parent_id');
     }
+
+    public function userTypePermissions(): HasMany
+    {
+        return $this->hasMany(UserTypePermission::class);
+    }
 }

+ 2 - 0
app/Models/PersonalAccessToken.php

@@ -9,6 +9,7 @@ class PersonalAccessToken extends \Laravel\Sanctum\PersonalAccessToken
     protected $casts = [
         'abilities' => 'array',
         'expires_at' => 'datetime',
+        'last_used_at' => 'datetime',
     ];
 
     protected $hidden = [
@@ -19,5 +20,6 @@ class PersonalAccessToken extends \Laravel\Sanctum\PersonalAccessToken
         'created_at',
         'updated_at',
         'expires_at',
+        'last_used_at',
     ];
 }

+ 38 - 0
app/Models/State.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Models;
+
+use App\Enums\DefaultStatusEnum;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class State extends Model
+{
+    use HasFactory;
+
+    protected $table = 'states';
+
+    protected $guarded = ['id'];
+
+        /**
+     * Get the attributes that should be cast.
+     *
+     * @return array<string, string>
+     */
+    protected function casts(): array
+    {
+        return [
+            'status' => DefaultStatusEnum::class,
+        ];
+    }
+
+    public function country()
+    {
+        return $this->belongsTo(Country::class, 'country_id');
+    }
+
+    public function cities()
+    {
+        return $this->hasMany(City::class, 'state_id');
+    }
+}

+ 4 - 12
app/Models/User.php

@@ -2,7 +2,7 @@
 
 namespace App\Models;
 
-use App\Enums\UserLanguageEnum;
+use App\Enums\LanguageEnum;
 use App\Enums\UserTypeEnum;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -14,16 +14,8 @@ class User extends Authenticatable
 {
     use HasFactory, Notifiable, HasApiTokens;
 
-    /**
-     * The attributes that are mass assignable.
-     *
-     * @var array<int, string>
-     */
-    protected $fillable = [
-        'name',
-        'email',
-        'password',
-    ];
+
+    protected $guarded = ['id'];
 
     /**
      * The attributes that should be hidden for serialization.
@@ -46,7 +38,7 @@ protected function casts(): array
             'email_verified_at' => 'datetime',
             'password' => 'hashed',
             'type' => UserTypeEnum::class,
-            'language' => UserLanguageEnum::class,
+            'language' => LanguageEnum::class,
         ];
     }
 

+ 2 - 9
app/Models/UserTypePermission.php

@@ -12,15 +12,7 @@ class UserTypePermission extends Model
 {
     use HasFactory, SoftDeletes;
 
-    /**
-     * The attributes that are mass assignable.
-     *
-     * @var array<int, string>
-     */
-    protected $fillable = [
-        'user_type',
-        'permission_id',
-    ];
+    protected $guarded = ['id'];
 
     /**
      * The attributes that should be cast.
@@ -32,6 +24,7 @@ protected function casts(): array
         return [
             'permission_id' => 'integer',
             'user_type' => UserTypeEnum::class,
+            'bits' => 'integer',
         ];
     }
 

+ 9 - 0
app/Providers/AppServiceProvider.php

@@ -14,6 +14,12 @@
 use App\Repositories\UserTypePermissionRepositoryInterface;
 use App\Repositories\PersonalAccessTokenRepository;
 use App\Repositories\PersonalAccessTokenRepositoryInterface;
+use App\Repositories\CityRepositoryInterface;
+use App\Repositories\CityRepository;
+use App\Repositories\StateRepositoryInterface;
+use App\Repositories\StateRepository;
+use App\Repositories\CountryRepositoryInterface;
+use App\Repositories\CountryRepository;
 
 class AppServiceProvider extends ServiceProvider
 {
@@ -28,6 +34,9 @@ class AppServiceProvider extends ServiceProvider
         UserTypePermissionRepositoryInterface::class => UserTypePermissionRepository::class,
         PersonalAccessTokenRepositoryInterface::class => PersonalAccessTokenRepository::class,
         AuthRepositoryInterface::class => AuthRepository::class,
+        CityRepositoryInterface::class => CityRepository::class,
+        StateRepositoryInterface::class => StateRepository::class,
+        CountryRepositoryInterface::class => CountryRepository::class,
         // Add other bindings here...
     ];
 

+ 48 - 0
app/Repositories/CityRepository.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Repositories;
+
+use App\Models\City;
+use App\DTO\CityDTO;
+use Illuminate\Database\Eloquent\Collection;
+
+class CityRepository implements CityRepositoryInterface
+{
+    public function __construct(
+        protected City $model
+    ) {}
+
+    public function all(): Collection
+    {
+        return $this->model->with('state', 'country')->get();
+    }
+
+    public function find(int $id): ?City
+    {
+        return $this->model->with('state', 'country')->find($id);
+    }
+
+    public function create(CityDTO $dto): City
+    {
+        return $this->model->create($dto->toArray());
+    }
+
+    public function update(int $id, CityDTO $dto, array $fieldsToUpdate): City
+    {
+        $record = $this->find($id);
+
+        $updateFields = array_intersect_key(
+            $dto->toArray(),
+            array_flip($fieldsToUpdate)
+        );
+
+        $record->update($updateFields);
+
+        return $record->fresh();
+    }
+
+    public function delete(int $id): bool
+    {
+        return $this->model->destroy($id) > 0;
+    }
+}

+ 20 - 0
app/Repositories/CityRepositoryInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Repositories;
+
+use App\DTO\CityDTO;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\City;
+
+interface CityRepositoryInterface
+{
+    public function all(): Collection;
+
+    public function find(int $id): ?City;
+
+    public function create(CityDTO $dto): City;
+
+    public function update(int $id, CityDTO $dto, array $fieldsToUpdate): City;
+
+    public function delete(int $id): bool;
+}

+ 49 - 0
app/Repositories/CountryRepository.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Repositories;
+
+use App\Models\Country;
+use App\DTO\CountryDTO;
+use Illuminate\Database\Eloquent\Collection;
+
+class CountryRepository implements CountryRepositoryInterface
+{
+    public function __construct(
+        protected Country $model
+    ){
+    }
+
+    public function all(): Collection
+    {
+        return $this->model->all();
+    }
+
+    public function find(int $id): ?Country
+    {
+        return $this->model->find($id);
+    }
+
+    public function create(CountryDTO $dto): Country
+    {
+        return $this->model->create($dto->toArray());
+    }
+
+    public function update(int $id, CountryDTO $dto, array $fieldsToUpdate): Country
+    {
+        $record = $this->find($id);
+
+        $updateFields = array_intersect_key(
+            $dto->toArray(),
+            array_flip($fieldsToUpdate)
+        );
+
+        $record->update($updateFields);
+
+        return $record->fresh();
+    }
+
+    public function delete(int $id): bool
+    {
+        return $this->model->destroy($id) > 0;
+    }
+}

+ 20 - 0
app/Repositories/CountryRepositoryInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Repositories;
+
+use App\DTO\CountryDTO;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\Country;
+
+interface CountryRepositoryInterface
+{
+    public function all(): Collection;
+
+    public function find(int $id): ?Country;
+
+    public function create(CountryDTO $dto): Country;
+
+    public function update(int $id, CountryDTO $dto, array $fieldsToUpdate): Country;
+
+    public function delete(int $id): bool;
+}

+ 49 - 0
app/Repositories/StateRepository.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Repositories;
+
+use App\Models\State;
+use App\DTO\StateDTO;
+use Illuminate\Database\Eloquent\Collection;
+
+class StateRepository implements StateRepositoryInterface
+{
+    public function __construct(
+        protected State $model
+    ){
+    }
+
+    public function all(): Collection
+    {
+        return $this->model->with('country')->get();
+    }
+
+    public function find(int $id): ?State
+    {
+        return $this->model->with('country')->find($id);
+    }
+
+    public function create(StateDTO $dto): State
+    {
+        return $this->model->create($dto->toArray());
+    }
+
+    public function update(int $id, StateDTO $dto, array $fieldsToUpdate): State
+    {
+        $record = $this->find($id);
+
+        $updateFields = array_intersect_key(
+            $dto->toArray(),
+            array_flip($fieldsToUpdate)
+        );
+
+        $record->update($updateFields);
+
+        return $record->fresh();
+    }
+
+    public function delete(int $id): bool
+    {
+        return $this->model->destroy($id) > 0;
+    }
+}

+ 20 - 0
app/Repositories/StateRepositoryInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Repositories;
+
+use App\DTO\StateDTO;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\State;
+
+interface StateRepositoryInterface
+{
+    public function all(): Collection;
+
+    public function find(int $id): ?State;
+
+    public function create(StateDTO $dto): State;
+
+    public function update(int $id, StateDTO $dto, array $fieldsToUpdate): State;
+
+    public function delete(int $id): bool;
+}

+ 11 - 8
app/Repositories/UserTypePermissionRepository.php

@@ -5,6 +5,7 @@
 use App\Models\UserTypePermission;
 use App\Enums\UserTypeEnum;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\Log;
 
 class UserTypePermissionRepository implements UserTypePermissionRepositoryInterface
 {
@@ -16,16 +17,18 @@ public function __construct(
     public function allGuestPermissions(): ?Collection
     {
         return $this->model->where('user_type', UserTypeEnum::GUEST)
-        ->join('permissions', 'user_type_permissions.permission_id', '=',  'permissions.id')
-        ->select('permissions.*', 'user_type_permissions.bits', 'user_type_permissions.*')
-        ->get();
+            ->with('permission')
+            ->get();
     }
 
-    public function allPermissionsByUserType(UserTypeEnum $userType): ?Collection
+    public function allPermissionsByUserType(UserTypeEnum $userType): Collection
     {
-        return $this->model->where('user_type', $userType)
-        ->join('permissions',  'user_type_permissions.permission_id',  '=',  'permissions.id')
-        ->select('permissions.*', 'user_type_permissions.bits', 'user_type_permissions.*')
-        ->get();
+        $return = $this->model
+            ->where('user_type', $userType)
+            ->with('permission')
+            ->get();
+
+
+        return $return;
     }
 }

+ 41 - 0
app/Services/CityService.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Services;
+
+use App\Repositories\CityRepositoryInterface;
+use App\DTO\CityDTO;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\City;
+
+class CityService
+{
+    public function __construct(
+        protected CityRepositoryInterface $repository
+    ){
+    }
+
+    public function getAllItems(): Collection
+    {
+        return $this->repository->all();
+    }
+
+    public function getItem(int $id): ?City
+    {
+        return $this->repository->find($id);
+    }
+
+    public function createItem(CityDTO $dto): City
+    {
+        return $this->repository->create($dto);
+    }
+
+    public function updateItem(CityDTO $dto, int $id): City
+    {
+        return $this->repository->update($id, $dto, request()->keys());
+    }
+
+    public function deleteItem(int $id): bool
+    {
+        return $this->repository->delete($id);
+    }
+}

+ 41 - 0
app/Services/CountryService.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Services;
+
+use App\Repositories\CountryRepositoryInterface;
+use App\DTO\CountryDTO;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\Country;
+
+class CountryService
+{
+    public function __construct(
+        protected CountryRepositoryInterface $repository
+    ){
+    }
+
+    public function getAllItems(): Collection
+    {
+        return $this->repository->all();
+    }
+
+    public function getItem(int $id): ?Country
+    {
+        return $this->repository->find($id);
+    }
+
+    public function createItem(CountryDTO $dto): Country
+    {
+        return $this->repository->create($dto);
+    }
+
+    public function updateItem(CountryDTO $dto, int $id): Country
+    {
+        return $this->repository->update($id, $dto, request()->keys());
+    }
+
+    public function deleteItem(int $id): bool
+    {
+        return $this->repository->delete($id);
+    }
+}

+ 41 - 0
app/Services/StateService.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Services;
+
+use App\Repositories\StateRepositoryInterface;
+use App\DTO\StateDTO;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\State;
+
+class StateService
+{
+    public function __construct(
+        protected StateRepositoryInterface $repository
+    ){
+    }
+
+    public function getAllItems(): Collection
+    {
+        return $this->repository->all();
+    }
+
+    public function getItem(int $id): ?State
+    {
+        return $this->repository->find($id);
+    }
+
+    public function createItem(StateDTO $dto): State
+    {
+        return $this->repository->create($dto);
+    }
+
+    public function updateItem(StateDTO $dto, int $id): State
+    {
+        return $this->repository->update($id, $dto, request()->keys());
+    }
+
+    public function deleteItem(int $id): bool
+    {
+        return $this->repository->delete($id);
+    }
+}

+ 6 - 0
app/Services/UserService.php

@@ -5,6 +5,7 @@
 use Illuminate\Database\Eloquent\Collection;
 use App\Repositories\UserRepositoryInterface;
 use App\DTO\UserDTO;
+use App\Enums\UserTypeEnum;
 use App\Models\User;
 
 class UserService
@@ -52,4 +53,9 @@ public function updateLanguage(UserDTO $dto, int $id): ?User
     {
         return $this->userRepository->update(id: $id, dto: $dto, fieldsToUpdate: ['language']);
     }
+
+    public function getUserTypes(): array
+    {
+        return UserTypeEnum::toArray();
+    }
 }

+ 74 - 0
app/Traits/Base64ToFile.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Traits;
+
+use Illuminate\Http\UploadedFile;
+use InvalidArgumentException;
+use RuntimeException;
+
+trait Base64ToFile
+{
+    /**
+     * Convert a base64 string to a file
+     *
+     * @param string $base64String The base64 encoded string
+     * @param string $fileName Optional filename (without extension)
+     * @return UploadedFile
+     * @throws InvalidArgumentException If the base64 string is invalid
+     * @throws RuntimeException If file creation fails
+     */
+    protected function convertBase64ToFile(string $base64String, ?string $fileName = null): UploadedFile
+    {
+        if (!preg_match('/^data:([^;]+);base64,(.+)$/', $base64String, $matches)) {
+            throw new InvalidArgumentException('Invalid base64 string format');
+        }
+
+        $mimeType = $matches[1];
+        $base64Content = $matches[2];
+
+        $fileContent = base64_decode($base64Content, true);
+        if ($fileContent === false) {
+            throw new InvalidArgumentException('Failed to decode base64 content');
+        }
+
+        $extension = $this->getExtensionFromMimeType($mimeType);
+
+        $fileName = $fileName ?? uniqid('file_', true);
+        $fullFileName = "{$fileName}.{$extension}";
+
+        $tempPath = sys_get_temp_dir() . '/' . $fullFileName;
+        if (file_put_contents($tempPath, $fileContent) === false) {
+            throw new RuntimeException('Failed to create temporary file');
+        }
+
+        return new UploadedFile(
+            $tempPath,
+            $fullFileName,
+            $mimeType,
+            null,
+            true
+        );
+    }
+
+    /**
+     * Get file extension from mime type
+     *
+     * @param string $mimeType
+     * @return string
+     */
+    private function getExtensionFromMimeType(string $mimeType): string
+    {
+        $mimeToExt = [
+            'image/jpeg' => 'jpg',
+            'image/png' => 'png',
+            'image/gif' => 'gif',
+            'application/pdf' => 'pdf',
+            'text/plain' => 'txt',
+            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+            'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+            'application/vnd.ms-excel' => 'xls',
+        ];
+
+        return $mimeToExt[$mimeType] ?? 'bin';
+    }
+}

+ 1 - 1
app/Traits/EnumHelper.php

@@ -14,7 +14,7 @@ public static function toArray(): array
         return array_column(self::cases(), 'value');
     }
 
-    public static function fromString(string $value): static
+    public static function fromString(string $value): ?static
     {
         $cases = array_combine(array_map(fn($case) => $case->value, self::cases()), self::cases());
         return $cases[$value] ?? null;

+ 2 - 2
composer.json

@@ -5,9 +5,9 @@
     "keywords": ["laravel", "framework"],
     "license": "MIT",
     "require": {
-        "php": "^8.2",
+        "php": "^8.3",
         "kalnoy/nestedset": "^6.0",
-        "laravel/framework": "^11.9",
+        "laravel/framework": "^12.0",
         "laravel/sanctum": "^4.0",
         "laravel/tinker": "^2.9"
     },

Fișier diff suprimat deoarece este prea mare
+ 224 - 207
composer.lock


+ 87 - 0
config/broadcasting.php

@@ -0,0 +1,87 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Broadcaster
+    |--------------------------------------------------------------------------
+    |
+    | This option controls the default broadcaster that will be used by the
+    | framework when an event needs to be broadcast. You may set this to
+    | any of the connections defined in the "connections" array below.
+    |
+    | Supported: "reverb", "pusher", "ably", "redis", "log", "null"
+    |
+    */
+
+    'default' => env('BROADCAST_CONNECTION', 'null'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Broadcast Connections
+    |--------------------------------------------------------------------------
+    |
+    | Here you may define all of the broadcast connections that will be used
+    | to broadcast events to other systems or over WebSockets. Samples of
+    | each available type of connection are provided inside this array.
+    |
+    */
+
+    'connections' => [
+
+        'reverb' => [
+            'driver' => 'reverb',
+            'key' => env('REVERB_APP_KEY'),
+            'secret' => env('REVERB_APP_SECRET'),
+            'app_id' => env('REVERB_APP_ID'),
+            'options' => [
+                'host' => env('REVERB_HOST'),
+                'port' => env('REVERB_PORT', 443),
+                'scheme' => env('REVERB_SCHEME', 'https'),
+                'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
+            ],
+            'client_options' => [
+                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
+            ],
+        ],
+
+        'pusher' => [
+            'driver' => 'pusher',
+            'key' => env('PUSHER_APP_KEY'),
+            'secret' => env('PUSHER_APP_SECRET'),
+            'app_id' => env('PUSHER_APP_ID'),
+            'options' => [
+                'cluster' => env('PUSHER_APP_CLUSTER'),
+                'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
+                'port' => env('PUSHER_PORT', 443),
+                'scheme' => env('PUSHER_SCHEME', 'https'),
+                'encrypted' => true,
+                'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
+            ],
+            'client_options' => [
+                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
+            ],
+        ],
+
+        'ably' => [
+            'driver' => 'ably',
+            'key' => env('ABLY_KEY'),
+        ],
+
+        'log' => [
+            'driver' => 'log',
+        ],
+
+        'null' => [
+            'driver' => 'null',
+        ],
+
+        'redis' => [
+            'driver' => 'redis',
+            'connection' => 'default',
+        ],
+
+    ],
+
+];

+ 3 - 0
database/migrations/2024_07_02_185050_create_personal_access_tokens_table.php

@@ -20,6 +20,9 @@ public function up(): void
             $table->timestamp('last_used_at')->nullable();
             $table->timestamp('expires_at')->nullable();
             $table->timestamps();
+            $table->index(['id', 'token']);
+            $table->index(['expires_at', 'last_used_at']);
+            $table->index(['tokenable_type']);
         });
     }
 

+ 3 - 0
database/migrations/2024_07_16_175714_create_permissions_and_user_type_permissions_table.php

@@ -14,10 +14,12 @@ public function up(): void
             $table->string('description');
             $table->integer('bits');
             $table->unsignedBigInteger('parent_id')->nullable();
+            $table->foreign('parent_id')->references('id')->on('permissions')->onDelete('cascade');
             $table->integer('_lft');
             $table->integer('_rgt');
             $table->timestamps();
             $table->softDeletes();
+            $table->index(['_lft', '_rgt']);
         });
 
         Schema::create('user_type_permissions', function (Blueprint $table) {
@@ -28,6 +30,7 @@ public function up(): void
             $table->integer('bits');
             $table->timestamps();
             $table->softDeletes();
+            $table->index(['user_type', 'permission_id']);
         });
     }
 

+ 25 - 0
database/migrations/2024_12_18_184014_create_countries_table.php

@@ -0,0 +1,25 @@
+<?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::create('countries', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->string('code', 3);
+            $table->enum('status', ['ACTIVE', 'INACTIVE'])->default('ACTIVE');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('countries');
+    }
+};

+ 27 - 0
database/migrations/2024_12_18_184015_create_states_table.php

@@ -0,0 +1,27 @@
+<?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::create('states', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->string('code', 2);
+            $table->foreignId('country_id')->constrained();
+            $table->index('country_id');
+            $table->enum('status', ['ACTIVE', 'INACTIVE'])->default('ACTIVE');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('states');
+    }
+};

+ 28 - 0
database/migrations/2024_12_18_184016_create_cities_table.php

@@ -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::create('cities', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->foreignId('country_id')->constrained();
+            $table->foreignId('state_id')->constrained();
+            $table->index('country_id');
+            $table->index('state_id');
+            $table->enum('status', ['ACTIVE', 'INACTIVE'])->default('ACTIVE');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('cities');
+    }
+};

+ 69 - 0
database/seeders/BrazilCitiesSeeder.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Database\Seeders;
+
+use App\Models\City;
+use App\Models\State;
+use App\Models\Country;
+use GuzzleHttp\Client;
+use Illuminate\Database\Seeder;
+
+class BrazilCitiesSeeder extends Seeder
+{
+  public function run()
+  {
+
+    try {
+      $brasil = Country::firstOrNew(
+        [
+            'name' => 'Brasil',
+            'code' => 'BR'
+        ]
+      );
+      $brasil->save();
+
+      $client_estados = new Client();
+      $url_estados = 'https://servicodados.ibge.gov.br/api/v1/localidades/estados';
+      $response_estados = $client_estados->request('GET', $url_estados);
+
+      $estados = json_decode($response_estados->getBody());
+
+      foreach ($estados as $estado) {
+        echo "Criando estado {$estado->nome}\n";
+
+        $new_estado = State::firstOrNew(
+          [
+            'name' => $estado->nome,
+            'code' => $estado->sigla,
+            'country_id' => $brasil->id
+          ]
+        );
+        $new_estado->save();
+
+        $client_cidades = new Client();
+        $url_cidades = "https://servicodados.ibge.gov.br/api/v1/localidades/estados/$estado->id/municipios";
+        $response_cidades = $client_cidades->request('GET', $url_cidades);
+
+        $cidades = json_decode($response_cidades->getBody());
+
+        $total_cidades = 0;
+        foreach ($cidades as $cidade) {
+
+          $new_cidade = City::firstOrNew(
+            [
+                'name' => $cidade->nome,
+                'state_id' => $new_estado->id,
+                'country_id' => $brasil->id
+            ]
+          );
+          $new_cidade->save();
+          $total_cidades++;
+        }
+        echo " -> Total de cidades: {$total_cidades}\n";
+      }
+    } catch (\Throwable $th) {
+      throw $th;
+      return false;
+    }
+  }
+}

+ 1 - 0
database/seeders/DatabaseSeeder.php

@@ -15,6 +15,7 @@ public function run(): void
             UserSeeder::class,
             PermissionSeeder::class,
             UserTypePermissionSeeder::class,
+            BrazilCitiesSeeder::class,
         ]);
     }
 }

+ 19 - 1
database/seeders/PermissionSeeder.php

@@ -52,7 +52,25 @@ public function run(): void
                         'description' => 'Configurações de Permissões',
                         'bits' => 271,
                         'children' => []
-                    ]
+                    ],
+                    [
+                        'scope' => 'config.city',
+                        'description' => 'Configurações de Cidades',
+                        'bits' => 271,
+                        'children' => []
+                    ],
+                    [
+                        'scope' => 'config.country',
+                        'description' => 'Configurações de Países',
+                        'bits' => 271,
+                        'children' => []
+                    ],
+                    [
+                        'scope' => 'config.state',
+                        'description' => 'Configurações de Estados',
+                        'bits' => 271,
+                        'children' => []
+                    ],
                 ],
             ],
         ];

+ 0 - 2
database/seeders/UserSeeder.php

@@ -19,7 +19,5 @@ public function run(): void
             'password' => 'S@ft2080.',
             'type' => UserTypeEnum::ADMIN,
         ]);
-
-        $user->save();
     }
 }

+ 40 - 0
lang/en/http.php

@@ -0,0 +1,40 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Http Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used during http for various
+    | messages that we need to display to the user. You are free to modify
+    | these language lines according to your application's requirements.
+    |
+    */
+
+    'created' => 'Created successfully',
+    'updated' => 'Updated successfully',
+    'deleted' => 'Deleted successfully',
+    'success' => 'Success',
+    'error' => 'Error',
+    'not_found' => 'Not found',
+    'unauthorized' => 'Unauthorized',
+    'forbidden' => 'Forbidden',
+    'validation_error' => 'Validation error',
+    'internal_server_error' => 'Internal server error',
+    'bad_request' => 'Bad request',
+    'no_content' => 'No content',
+    'conflict' => 'Conflict',
+    'unprocessable_entity' => 'Unprocessable entity',
+    'too_many_requests' => 'Too many requests',
+    'service_unavailable' => 'Service unavailable',
+    'gateway_timeout' => 'Gateway timeout',
+    'not_implemented' => 'Not implemented',
+    'not_acceptable' => 'Not acceptable',
+    'not_allowed' => 'Method not allowed',
+    'not_acceptable' => 'Not acceptable',
+    'webhook_received' => 'Webhook received',
+    'unauthorized_ip' => 'Unauthorized IP address',
+    'unauthorized_token' => 'Invalid webhook token',
+];

+ 5 - 0
lang/en/messages.php

@@ -5,4 +5,9 @@
     'created' => 'Created successfully',
     'updated' => 'Updated successfully',
     'deleted' => 'Deleted successfully',
+    'email_sent' => 'Email sent successfully',
+    'email_not_sent' => 'Email not sent',
+    'imported' => 'Imported successfully',
+    'import_error' => 'Error importing',
+    'buyer_not_allowed' => 'Buyer not allowed',
 ];

+ 4 - 0
lang/en/validation.php

@@ -33,6 +33,7 @@
     ],
     'boolean' => 'The :attribute field must be true or false.',
     'can' => 'The :attribute field contains an unauthorized value.',
+    'cannot_delete_related' => 'The :attribute field cannot be deleted because it has related records.',
     'confirmed' => 'The :attribute field confirmation does not match.',
     'contains' => 'The :attribute field is missing a required value.',
     'current_password' => 'The password is incorrect.',
@@ -52,6 +53,7 @@
     'email' => 'The :attribute field must be a valid email address.',
     'ends_with' => 'The :attribute field must end with one of the following: :values.',
     'enum' => 'The selected :attribute is invalid.',
+    'event_full' => 'The selected event is full.',
     'exists' => 'The selected :attribute is invalid.',
     'extensions' => 'The :attribute field must have one of the following extensions: :values.',
     'file' => 'The :attribute field must be a file.',
@@ -124,6 +126,7 @@
         'symbols' => 'The :attribute field must contain at least one symbol.',
         'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
     ],
+    'payment_cant_be_refunded' => 'This payment cannot be refunded.',
     'present' => 'The :attribute field must be present.',
     'present_if' => 'The :attribute field must be present when :other is :value.',
     'present_unless' => 'The :attribute field must be present unless :other is :value.',
@@ -133,6 +136,7 @@
     'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
     'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
     'prohibits' => 'The :attribute field prohibits :other from being present.',
+    'refund_value_greater_than_payment_value' => 'The refund value cannot be greater than the payment value.',
     'regex' => 'The :attribute field format is invalid.',
     'required' => 'The :attribute field is required.',
     'required_array_keys' => 'The :attribute field must contain entries for: :values.',

+ 39 - 0
lang/es/http.php

@@ -0,0 +1,39 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Líneas de Lenguaje HTTP
+    |--------------------------------------------------------------------------
+    |
+    | Las siguientes líneas de lenguaje se utilizan durante las solicitudes HTTP
+    | para varios mensajes que necesitamos mostrar al usuario. Eres libre de
+    | modificar estas líneas de lenguaje según los requisitos de tu aplicación.
+    |
+    */
+
+    'created' => 'Creado exitosamente',
+    'updated' => 'Actualizado exitosamente',
+    'deleted' => 'Eliminado exitosamente',
+    'success' => 'Éxito',
+    'error' => 'Error',
+    'not_found' => 'No encontrado',
+    'unauthorized' => 'No autorizado',
+    'forbidden' => 'Prohibido',
+    'validation_error' => 'Error de validación',
+    'internal_server_error' => 'Error interno del servidor',
+    'bad_request' => 'Solicitud incorrecta',
+    'no_content' => 'Sin contenido',
+    'conflict' => 'Conflicto',
+    'unprocessable_entity' => 'Entidad no procesable',
+    'too_many_requests' => 'Demasiadas solicitudes',
+    'service_unavailable' => 'Servicio no disponible',
+    'gateway_timeout' => 'Tiempo de espera de la puerta de enlace',
+    'not_implemented' => 'No implementado',
+    'not_acceptable' => 'No aceptable',
+    'not_allowed' => 'Método no permitido',
+    'webhook_received' => 'Webhook recibido',
+    'unauthorized_ip' => 'IP no autorizada',
+    'unauthorized_token' => 'Token no autorizado',
+];

+ 5 - 0
lang/es/messages.php

@@ -5,4 +5,9 @@
     'created' => 'Creado exitosamente',
     'updated' => 'Actualizado exitosamente',
     'deleted' => 'Eliminado exitosamente',
+    'email_sent' => 'Email enviado exitosamente',
+    'email_not_sent' => 'Email no enviado',
+    'imported' => 'Importado exitosamente',
+    'import_error' => 'Error al importar',
+    'buyer_not_allowed' => 'Comprador no permitido',
 ];

+ 4 - 0
lang/es/validation.php

@@ -33,6 +33,7 @@
     ],
     'boolean' => 'El campo :attribute debe ser verdadero o falso.',
     'can' => 'El campo :attribute contiene un valor no autorizado.',
+    'cannot_delete_related' => 'No se puede eliminar el registro porque tiene registros relacionados.',
     'confirmed' => 'La confirmación del campo :attribute no coincide.',
     'contains' => 'El campo :attribute carece de un valor requerido.',
     'current_password' => 'La contraseña es incorrecta.',
@@ -52,6 +53,7 @@
     'email' => 'El campo :attribute debe ser una dirección de correo electrónico válida.',
     'ends_with' => 'El campo :attribute debe terminar con uno de los siguientes: :values.',
     'enum' => 'El campo :attribute seleccionado no es válido.',
+    'event_full' => 'El evento seleccionado está completo.',
     'exists' => 'El campo :attribute seleccionado no es válido.',
     'extensions' => 'El campo :attribute debe tener una de las siguientes extensiones: :values.',
     'file' => 'El campo :attribute debe ser un archivo.',
@@ -124,6 +126,7 @@
         'symbols' => 'El campo :attribute debe contener al menos un símbolo.',
         'uncompromised' => 'El campo :attribute proporcionado ha aparecido en una filtración de datos. Elija un :attribute diferente.',
     ],
+    'payment_cant_be_refunded' => 'El pago no puede ser reembolsado.',
     'present' => 'El campo :attribute debe estar presente.',
     'present_if' => 'El campo :attribute debe estar presente cuando :other es :value.',
     'present_unless' => 'El campo :attribute debe estar presente a menos que :other sea :value.',
@@ -133,6 +136,7 @@
     'prohibited_if' => 'El campo :attribute está prohibido cuando :other es :value.',
     'prohibited_unless' => 'El campo :attribute está prohibido a menos que :other esté en :values.',
     'prohibits' => 'El campo :attribute prohíbe que :other esté presente.',
+    'refund_value_greater_than_payment_value' => 'El valor del reembolso no puede ser mayor que el valor del pago.',
     'regex' => 'El formato del campo :attribute no es válido.',
     'required' => 'El campo :attribute es obligatorio.',
     'required_array_keys' => 'El campo :attribute debe contener entradas para: :values.',

+ 39 - 0
lang/pt/http.php

@@ -0,0 +1,39 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Linhas de Linguagem HTTP
+    |--------------------------------------------------------------------------
+    |
+    | As seguintes linhas de linguagem são utilizadas durante as solicitações HTTP
+    | para várias mensagens que precisamos mostrar ao usuário. Você é livre para
+    | modificar essas linhas de linguagem conforme os requisitos da sua aplicação.
+    |
+    */
+
+    'created' => 'Criado com sucesso',
+    'updated' => 'Atualizado com sucesso',
+    'deleted' => 'Excluído com sucesso',
+    'success' => 'Sucesso',
+    'error' => 'Erro',
+    'not_found' => 'Não encontrado',
+    'unauthorized' => 'Não autorizado',
+    'forbidden' => 'Proibido',
+    'validation_error' => 'Erro de validação',
+    'internal_server_error' => 'Erro interno do servidor',
+    'bad_request' => 'Solicitação incorreta',
+    'no_content' => 'Sem conteúdo',
+    'conflict' => 'Conflito',
+    'unprocessable_entity' => 'Entidade não processável',
+    'too_many_requests' => 'Muitas solicitações',
+    'service_unavailable' => 'Serviço indisponível',
+    'gateway_timeout' => 'Tempo de espera do gateway',
+    'not_implemented' => 'Não implementado',
+    'not_acceptable' => 'Não aceitável',
+    'not_allowed' => 'Método não permitido',
+    'webhook_received' => 'Webhook recebido',
+    'unauthorized_ip' => 'Endereço IP não autorizado',
+    'unauthorized_token' => 'Token de webhook inválido',
+];

+ 5 - 0
lang/pt/messages.php

@@ -5,4 +5,9 @@
     'created' => 'Criado com sucesso',
     'updated' => 'Atualizado com sucesso',
     'deleted' => 'Excluído com sucesso',
+    'email_sent' => 'Email enviado com sucesso',
+    'email_not_sent' => 'Email não enviado',
+    'imported' => 'Importado com sucesso',
+    'import_error' => 'Erro ao importar',
+    'buyer_not_allowed' => 'Compra não permitida, tente outro ingresso ou comprador',
 ];

+ 4 - 0
lang/pt/validation.php

@@ -34,6 +34,7 @@
     ],
     'boolean' => 'O campo :attribute deve ser verdadeiro ou falso.',
     'can' => 'O campo :attribute contém um valor não autorizado.',
+    'cannot_delete_related' => 'Não é possível excluir o :attribute porque existem registros relacionados.',
     'confirmed' => 'A confirmação do campo :attribute não corresponde.',
     'contains' => 'O campo :attribute está faltando um valor obrigatório.',
     'current_password' => 'A senha está incorreta.',
@@ -53,6 +54,7 @@
     'email' => 'O campo :attribute deve ser um endereço de e-mail válido.',
     'ends_with' => 'O campo :attribute deve terminar com um dos seguintes: :values.',
     'enum' => 'O campo :attribute selecionado é inválido.',
+    'event_full' => 'O evento está lotado.',
     'exists' => 'O campo :attribute selecionado é inválido.',
     'extensions' => 'O campo :attribute deve ter uma das seguintes extensões: :values.',
     'file' => 'O campo :attribute deve ser um arquivo.',
@@ -125,6 +127,7 @@
         'symbols' => 'O campo :attribute deve conter pelo menos um símbolo.',
         'uncompromised' => 'O campo :attribute fornecido apareceu em um vazamento de dados. Escolha um :attribute diferente.',
     ],
+    'payment_cant_be_refunded' => 'O pagamento não pode ser reembolsado.',
     'present' => 'O campo :attribute deve estar presente.',
     'present_if' => 'O campo :attribute deve estar presente quando :other é :value.',
     'present_unless' => 'O campo :attribute deve estar presente a menos que :other seja :value.',
@@ -134,6 +137,7 @@
     'prohibited_if' => 'O campo :attribute é proibido quando :other é :value.',
     'prohibited_unless' => 'O campo :attribute é proibido a menos que :other esteja em :values.',
     'prohibits' => 'O campo :attribute proíbe :other de estar presente.',
+    'refund_value_greater_than_payment_value' => 'O valor do reembolso não pode ser maior que o valor do pagamento.',
     'regex' => 'O formato do campo :attribute é inválido.',
     'required' => 'O campo :attribute é obrigatório.',
     'required_array_keys' => 'O campo :attribute deve conter entradas para: :values.',

+ 18 - 0
routes/authRoutes/city.php

@@ -0,0 +1,18 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\CityController;
+
+Route::get('/city', [CityController::class, 'index'])->middleware('permission:config.city,view');
+
+Route::post('/city', [CityController::class, 'store'])->middleware('permission:config.city,add');
+
+Route::get('/city/{id}', [CityController::class, 'show'])->middleware('permission:config.city,view');
+
+Route::put('/city/{id}', [CityController::class, 'update'])->middleware('permission:config.city,edit');
+
+Route::delete('/city/{id}', [CityController::class, 'destroy'])->middleware('permission:config.city,delete');
+
+Route::get('/city-state/{id}', [CityController::class, 'allByStateId'])->middleware('permission:config.city,view');
+
+Route::get('/city-country/{id}', [CityController::class, 'allByCountryId'])->middleware('permission:config.city,view');

+ 14 - 0
routes/authRoutes/country.php

@@ -0,0 +1,14 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\CountryController;
+
+Route::get('/country', [CountryController::class, 'index'])->middleware('permission:config.country,view');
+
+Route::post('/country', [CountryController::class, 'store'])->middleware('permission:config.country,add');
+
+Route::get('/country/{id}', [CountryController::class, 'show'])->middleware('permission:config.country,view');
+
+Route::put('/country/{id}', [CountryController::class, 'update'])->middleware('permission:config.country,edit');
+
+Route::delete('/country/{id}', [CountryController::class, 'destroy'])->middleware('permission:config.country,delete');

+ 16 - 0
routes/authRoutes/state.php

@@ -0,0 +1,16 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\StateController;
+
+Route::get('/state', [StateController::class, 'index'])->middleware('permission:config.state,view');
+
+Route::post('/state', [StateController::class, 'store'])->middleware('permission:config.state,add');
+
+Route::get('/state/{id}', [StateController::class, 'show'])->middleware('permission:config.state,view');
+
+Route::put('/state/{id}', [StateController::class, 'update'])->middleware('permission:config.state,edit');
+
+Route::delete('/state/{id}', [StateController::class, 'destroy'])->middleware('permission:config.state,delete');
+
+Route::get('/state-country/{id}', [StateController::class, 'allByCountryId'])->middleware('permission:config.state,view');

+ 4 - 2
routes/authRoutes/user.php

@@ -9,10 +9,12 @@
 
 Route::post('/user', [UserController::class, 'store'])->middleware('permission:config.user,add');
 
-Route::get('/user/{id}', [UserController::class, 'show']);
+Route::get('/user/{id}', [UserController::class, 'show'])->middleware('permission:config.user,view');
 
 Route::put('/user/{id}', [UserController::class, 'update'])->middleware('permission:config.user,edit');
 
 Route::delete('/user/{id}', [UserController::class, 'destroy'])->middleware('permission:config.user,delete');
 
-Route::put('/user/language/{id}', [UserController::class, 'updateLanguage']);
+Route::put('/user-language/{id}', [UserController::class, 'updateLanguage'])->middleware('permission:config.user,edit');
+
+Route::get('/user-types', [UserController::class, 'getUserTypes'])->middleware('permission:config.user,view');

+ 15 - 0
routes/console.php

@@ -3,6 +3,8 @@
 use Illuminate\Foundation\Inspiring;
 use Illuminate\Support\Facades\Artisan;
 use App\Commands\CreateCrud;
+use App\Commands\RefreshPermissions;
+use App\Commands\TestWebsocketEvent;
 
 Artisan::command('inspire', function () {
     $this->comment(Inspiring::quote());
@@ -11,3 +13,16 @@
 Artisan::command('create:crud {name}', function ($name) {
     $this->call(CreateCrud::class, ['name' => $name]);
 })->purpose('Create a CRUD for a given model');
+
+Artisan::command('permissions:refresh', function () {
+    $this->call(RefreshPermissions::class);
+})->purpose('Refresh all permissions and user type permissions');
+
+Artisan::command('websocket:test {room} {--event=test-event} {--data=}', function () {
+    // Pass through all arguments and options to the command class
+    $this->call(TestWebsocketEvent::class, [
+        'room' => $this->argument('room'),
+        '--event' => $this->option('event'),
+        '--data' => $this->option('data'),
+    ]);
+})->purpose('Test websocket broadcasting by emitting an event');

+ 14 - 0
serve.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Create a named pipe for output handling
+PIPE=$(mktemp -u)
+mkfifo $PIPE
+
+# Start handling the output in background
+cat $PIPE | grep -v '^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\|.*\.\{3\}\)' &
+
+# Run artisan serve and redirect its output to our pipe
+php artisan serve > $PIPE 2>&1
+
+# Cleanup
+rm $PIPE

+ 2 - 2
storage/stubs/Model.stub

@@ -11,7 +11,7 @@ class {{modelName}} extends Model
 
     protected $table = '{{tableNameSnakeCase}}';
 
-    protected $fillable = [
-        // Add fillable attributes here
+    protected $protected = [
+        'id', // Add more fields that shouldn't be edited here
     ];
 }

+ 5 - 5
storage/stubs/Route.stub

@@ -3,12 +3,12 @@
 use Illuminate\Support\Facades\Route;
 use App\Http\Controllers\{{modelName}}Controller;
 
-Route::get('/{{modelNameSnakeCase}}', [{{modelName}}Controller::class, 'index']);
+Route::get('/{{modelNameSnakeCase}}', [{{modelName}}Controller::class, 'index'])->middleware('permission:config.{{modelNameSnakeCase}},view');
 
-Route::post('/{{modelNameSnakeCase}}', [{{modelName}}Controller::class, 'store']);
+Route::post('/{{modelNameSnakeCase}}', [{{modelName}}Controller::class, 'store'])->middleware('permission:config.{{modelNameSnakeCase}},add');
 
-Route::get('/{{modelNameSnakeCase}}/{id}', [{{modelName}}Controller::class, 'show']);
+Route::get('/{{modelNameSnakeCase}}/{id}', [{{modelName}}Controller::class, 'show'])->middleware('permission:config.{{modelNameSnakeCase}},view');
 
-Route::put('/{{modelNameSnakeCase}}/{id}', [{{modelName}}Controller::class, 'update']);
+Route::put('/{{modelNameSnakeCase}}/{id}', [{{modelName}}Controller::class, 'update'])->middleware('permission:config.{{modelNameSnakeCase}},edit');
 
-Route::delete('/{{modelNameSnakeCase}}/{id}', [{{modelName}}Controller::class, 'destroy']);
+Route::delete('/{{modelNameSnakeCase}}/{id}', [{{modelName}}Controller::class, 'destroy'])->middleware('permission:config.{{modelNameSnakeCase}},delete');

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff