ソースを参照

feat: auth fix, create crud e DI em todas as camadas para manter o padrão

DenLopes 1 年間 前
コミット
bae5b9c21b

+ 169 - 0
app/Commands/CreateCrud.php

@@ -0,0 +1,169 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Filesystem\Filesystem;
+
+class CreateCrud extends Command
+{
+    protected $signature = 'create:crud {name}';
+    protected $description = 'Create CRUD operations including Model, Repository, Service, Controller, DTO, Request, Resource, and Collection';
+
+    protected $files;
+
+    public function __construct(Filesystem $files)
+    {
+        parent::__construct();
+        $this->files = $files;
+    }
+
+    public function handle()
+    {
+        $name = $this->argument('name');
+        $this->createModel($name);
+        $this->createRepositoryInterface($name);
+        $this->createRepository($name);
+        $this->createService($name);
+        $this->createController($name);
+        $this->createDto($name);
+        $this->createRequest($name);
+        $this->createResource($name);
+        $this->createCollection($name);
+        $this->updateAppServiceProvider($name);
+        $this->info('CRUD operations created successfully!');
+    }
+
+    protected function createModel($name)
+    {
+        $modelTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Model')
+        );
+
+        $this->put("app/Models/{$name}.php", $modelTemplate);
+    }
+
+    protected function createRepositoryInterface($name)
+    {
+        $interfaceTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('RepositoryInterface')
+        );
+
+        $this->put("app/Repositories/{$name}RepositoryInterface.php", $interfaceTemplate);
+    }
+
+    protected function createRepository($name)
+    {
+        $repositoryTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Repository')
+        );
+
+        $this->put("app/Repositories/{$name}Repository.php", $repositoryTemplate);
+    }
+
+    protected function createService($name)
+    {
+        $serviceTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Service')
+        );
+
+        $this->put("app/Services/{$name}Service.php", $serviceTemplate);
+    }
+
+    protected function createController($name)
+    {
+        $controllerTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Controller')
+        );
+
+        $this->put("app/Http/Controllers/{$name}Controller.php", $controllerTemplate);
+    }
+
+    protected function createDto($name)
+    {
+        $dtoTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Dto')
+        );
+
+        $this->put("app/DataTransferObjects/{$name}Dto.php", $dtoTemplate);
+    }
+
+    protected function createRequest($name)
+    {
+        $requestTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Request')
+        );
+
+        $this->put("app/Http/Requests/{$name}Request.php", $requestTemplate);
+    }
+
+    protected function createResource($name)
+    {
+        $resourceTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Resource')
+        );
+
+        $this->put("app/Http/Resources/{$name}Resource.php", $resourceTemplate);
+    }
+
+    protected function createCollection($name)
+    {
+        $collectionTemplate = str_replace(
+            ['{{modelName}}'],
+            [$name],
+            $this->getStub('Collection')
+        );
+
+        $this->put("app/Http/Resources/{$name}Collection.php", $collectionTemplate);
+    }
+
+    protected function updateAppServiceProvider($name)
+    {
+        $providerPath = app_path('Providers/AppServiceProvider.php');
+        $content = $this->files->get($providerPath);
+
+        // Add binding
+        $binding = "        {$name}RepositoryInterface::class => {$name}Repository::class,";
+        $content = preg_replace(
+            '/(public \$bindings = \[)/',
+            "$1\n$binding",
+            $content
+        );
+
+        // Add use statement
+        $useStatement = "use App\Repositories\\{$name}RepositoryInterface;\nuse App\Repositories\\{$name}Repository;";
+        $content = preg_replace(
+            '/(use Illuminate\\\Support\\\ServiceProvider;)/',
+            "$1\n\n{$useStatement}",
+            $content
+        );
+
+        $this->files->put($providerPath, $content);
+    }
+
+    protected function getStub($type)
+    {
+        return $this->files->get(storage_path("stubs/{$type}.stub"));
+    }
+
+    protected function put($path, $contents)
+    {
+        $this->files->put($path, $contents);
+    }
+}

+ 0 - 3
app/Enums/UserTypeSource.php

@@ -9,7 +9,4 @@ enum UserTypeSource: string
     use EnumHelper;
 
     case Admin = 'admin';
-    case RegularUser = 'regular_user';
-    case PayingUser = 'paying_user';
-    case Guest = 'guest';
 }

+ 0 - 15
app/Models/User.php

@@ -57,21 +57,6 @@ public function isAdmin(): bool
         return $this->type === UserTypeSource::Admin;
     }
 
-    public function isRegularUser(): bool
-    {
-        return $this->type === UserTypeSource::RegularUser;
-    }
-
-    public function isPayingUser(): bool
-    {
-        return $this->type === UserTypeSource::PayingUser;
-    }
-
-    public function isGuest(): bool
-    {
-        return $this->type === UserTypeSource::Guest;
-    }
-
     public function permissions(): BelongsToMany
     {
         return $this->belongsToMany(Permission::class, 'user_type_permissions', 'user_type', 'permission_id');

+ 4 - 1
app/Providers/AppServiceProvider.php

@@ -2,7 +2,9 @@
 
 namespace App\Providers;
 
-use App\Models\PersonalAccessToken;
+
+use App\Repositories\AuthRepository;
+use App\Repositories\AuthRepositoryInterface;
 use App\Repositories\PermissionRepository;
 use App\Repositories\PermissionRepositoryInterface;
 use Illuminate\Support\ServiceProvider;
@@ -25,6 +27,7 @@ class AppServiceProvider extends ServiceProvider
         PermissionRepositoryInterface::class => PermissionRepository::class,
         UserTypePermissionRepositoryInterface::class => UserTypePermissionRepository::class,
         PersonalAccessTokenRepositoryInterface::class => PersonalAccessTokenRepository::class,
+        AuthRepositoryInterface::class => AuthRepository::class,
         // Add other bindings here...
     ];
 

+ 79 - 0
app/Repositories/AuthRepository.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Repositories;
+
+use App\Models\User;
+use App\Models\PersonalAccessToken;
+use App\DataTransferObjects\AuthDto;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+
+class AuthRepository implements AuthRepositoryInterface
+{
+    public function __construct(
+        protected Auth $model,
+        protected User $userModel,
+        protected PersonalAccessToken $personalAccessTokenModel
+    ) {}
+
+    public function findUserByEmail(string $email): ?User
+    {
+        return $this->userModel->where('email', $email)->first();
+    }
+
+    public function findToken(string $token): ?PersonalAccessToken
+    {
+        return $this->personalAccessTokenModel->findToken($token);
+    }
+
+    public function createAccessToken(User $user, string $deviceId): string
+    {
+        return $user->createToken(
+            "access_token_{$deviceId}",
+            ['access'],
+            Carbon::now()->addMinutes(15)
+        )->plainTextToken;
+    }
+
+    public function createRefreshToken(User $user, string $deviceId): string
+    {
+        return $user->createToken(
+            "refresh_token_{$deviceId}",
+            ['refresh'],
+            Carbon::now()->addDays(30)
+        )->plainTextToken;
+    }
+
+    public function updateTokenExpiration(PersonalAccessToken $token, \DateTime $expirationTime): void
+    {
+        $token->update(['expires_at' => $expirationTime]);
+    }
+
+    public function deleteUserTokensByDevice(User $user, string $deviceId): void
+    {
+        $user->tokens()
+            ->where('name', 'like', "%_{$deviceId}")
+            ->delete();
+    }
+
+    public function attemptLogin(AuthDto $credentials): bool
+    {
+        return $this->model->attempt($credentials->toArray());
+    }
+
+    public function refreshToken(PersonalAccessToken $tokenModel, User $user, string $deviceId): array
+    {
+        return DB::transaction(function () use ($tokenModel, $user, $deviceId) {
+            $this->updateTokenExpiration($tokenModel, Carbon::now()->addMinutes(2));
+
+            $accessToken = $this->createAccessToken($user, $deviceId);
+            $refreshToken = $this->createRefreshToken($user, $deviceId);
+
+            return [
+                'access_token' => $accessToken,
+                'refresh_token' => $refreshToken,
+            ];
+        });
+    }
+}

+ 26 - 0
app/Repositories/AuthRepositoryInterface.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Repositories;
+
+use App\Models\User;
+use App\Models\PersonalAccessToken;
+use App\DataTransferObjects\AuthDto;
+
+interface AuthRepositoryInterface
+{
+    public function findUserByEmail(string $email): ?User;
+
+    public function findToken(string $token): ?PersonalAccessToken;
+
+    public function createAccessToken(User $user, string $deviceId): string;
+
+    public function createRefreshToken(User $user, string $deviceId): string;
+
+    public function updateTokenExpiration(PersonalAccessToken $token, \DateTime $expirationTime): void;
+
+    public function deleteUserTokensByDevice(User $user, string $deviceId): void;
+
+    public function attemptLogin(AuthDto $credentials): bool;
+
+    public function refreshToken(PersonalAccessToken $token, User $user, string $deviceId): array;
+}

+ 13 - 8
app/Repositories/PermissionRepository.php

@@ -8,29 +8,34 @@
 
 class PermissionRepository implements PermissionRepositoryInterface
 {
+
+    public function __construct(
+        protected Permission $model
+    ) {}
+
     public function all(): ?Collection
     {
-        return Permission::all()->toTree();
+        return $this->model->all()->toTree();
     }
 
     public function allNoTree(): ?Collection
     {
-        return Permission::all();
+        return $this->model->all();
     }
 
     public function find(int $id): ?Permission
     {
-        return Permission::find($id);
+        return $this->model->find($id);
     }
 
     public function findByScope(string $scope): ?Permission
     {
-        return Permission::where('scope', $scope)->first();
+        return $this->model->where('scope', $scope)->first();
     }
 
     public function update(PermissionDto $permissionDto, int $id): ?Permission
     {
-        $permission = Permission::find($id);
+        $permission = $this->model->find($id);
         $permission->update([
             'scope' => $permissionDto->scope,
             'description' => $permissionDto->description,
@@ -42,7 +47,7 @@ public function update(PermissionDto $permissionDto, int $id): ?Permission
 
     public function store(PermissionDto $permissionDto): Permission
     {
-        $permission = Permission::firstOrNew([
+        $permission = $this->model->firstOrNew([
             'scope' => $permissionDto->scope,
             'description' => $permissionDto->description,
             'bits' => $permissionDto->bits,
@@ -50,7 +55,7 @@ public function store(PermissionDto $permissionDto): Permission
         ]);
 
         if ($permissionDto->parent_id) {
-            $parent = Permission::find($permissionDto->parent_id);
+            $parent = $this->model->find($permissionDto->parent_id);
             $permission->prependToNode($parent);
         }
 
@@ -61,6 +66,6 @@ public function store(PermissionDto $permissionDto): Permission
 
     public function delete(int $id): bool
     {
-        return Permission::destroy($id) > 0;
+        return $this->model->destroy($id) > 0;
     }
 }

+ 6 - 1
app/Repositories/PersonalAccessTokenRepository.php

@@ -6,8 +6,13 @@
 
 class PersonalAccessTokenRepository implements PersonalAccessTokenRepositoryInterface
 {
+
+    public function __construct(
+        protected PersonalAccessToken $model
+    ) {}
+
     public function findToken(string $token): ?PersonalAccessToken
     {
-        return PersonalAccessToken::findToken($token);
+        return $this->model->findToken($token);
     }
 }

+ 18 - 10
app/Repositories/UserRepository.php

@@ -11,56 +11,64 @@
 
 class UserRepository implements UserRepositoryInterface
 {
+
+    public function __construct(
+        protected User $model,
+        protected Auth $auth
+    )
+    {
+    }
+
     public function me(): ?User
     {
-        return Auth::user();
+        return $this->auth->user();
     }
 
     public function all(): ?Collection
     {
-        return User::all();
+        return $this->model->all();
     }
 
     public function store(UserDto $userDto): User
     {
-        return User::create([
+        return $this->model->create([
             'name' => $userDto->name,
             'email' => $userDto->email,
             'password' => bcrypt($userDto->password),
-            'type' => $userDto->type ?? UserTypeSource::RegularUser,
+            'type' => $userDto->type,
         ]);
     }
 
     public function update(UserDto $userDto, int $id): ?User
     {
-        $user = User::findOrFail($id);
+        $user = $this->model->findOrFail($id);
         $user->update([
             'name' => $userDto->name,
             'email' => $userDto->email,
             'password' => bcrypt($userDto->password),
-            'type' => $userDto->type ?? UserTypeSource::RegularUser,
+            'type' => $userDto->type,
         ]);
         return $user;
     }
 
     public function delete(int $id): bool
     {
-        return User::destroy($id) > 0;
+        return $this->model->destroy($id);
     }
 
     public function find(int $id): ?User
     {
-        return User::findOrFail($id);
+        return $this->model->findOrFail($id);
     }
 
     public function findByEmail(string $email): ?User
     {
-        return User::where('email', $email)->first();
+        return $this->model->where('email', $email)->first();
     }
 
     public function updateLanguage(UserLanguageDto $languageDto, int $id): ?User
     {
-        $user = User::findOrFail($id);
+        $user = $this->model->findOrFail($id);
         $user->update([
             'language' => $languageDto->language,
         ]);

+ 7 - 2
app/Repositories/UserTypePermissionRepository.php

@@ -8,13 +8,18 @@
 
 class UserTypePermissionRepository implements UserTypePermissionRepositoryInterface
 {
+
+    public function __construct(
+        protected UserTypePermission $model
+    ) {}
+
     public function allGuestPermissions(): ?Collection
     {
-        return UserTypePermission::where('user_type', 'guest')->join('permissions', 'user_type_permissions.permission_id', '=', 'permissions.id')->get();
+        return $this->model->where('user_type', 'guest')->join('permissions', 'user_type_permissions.permission_id', '=', 'permissions.id')->get();
     }
 
     public function allPermissionsByUserType(UserTypeSource $userType): ?Collection
     {
-        return UserTypePermission::where('user_type', $userType)->join('permissions', 'user_type_permissions.permission_id', '=', 'permissions.id')->get();
+        return $this->model->where('user_type', $userType)->join('permissions', 'user_type_permissions.permission_id', '=', 'permissions.id')->get();
     }
 }

+ 31 - 25
app/Services/AuthService.php

@@ -2,42 +2,43 @@
 
 namespace App\Services;
 
-use App\Repositories\UserRepositoryInterface;
-use App\Repositories\PersonalAccessTokenRepositoryInterface;
-use Carbon\Carbon;
+use App\Repositories\AuthRepositoryInterface;
 use App\DataTransferObjects\AuthDto;
 use App\DataTransferObjects\RefreshTokenDto;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Str;
 
 class AuthService
 {
-
     public function __construct(
-        protected UserRepositoryInterface $userRepository,
-        protected PersonalAccessTokenRepositoryInterface $personalAccessTokenRepository,
+        protected AuthRepositoryInterface $authRepository,
     ) {
     }
 
     public function login(AuthDto $credentials): ?array
     {
-        if (!Auth::attempt($credentials->toArray())) {
+        if (!$this->authRepository->attemptLogin($credentials)) {
             return null;
         }
 
-        $user = $this->userRepository->findByEmail($credentials->email);
-
-        $user->tokens()->delete();
+        $user = $this->authRepository->findUserByEmail($credentials->email);
+        $deviceId = Str::uuid()->toString();
 
-        $accessToken = $user->createToken('access_token', ['access'], Carbon::now()->addMinutes(15))->plainTextToken;
-        $refreshToken = $user->createToken('refresh_token', ['refresh'], Carbon::now()->addDays(30))->plainTextToken;
+        $accessToken = $this->authRepository->createAccessToken($user, $deviceId);
+        $refreshToken = $this->authRepository->createRefreshToken($user, $deviceId);
 
-        return ['access_token' => $accessToken, 'refresh_token' => $refreshToken, 'user' => $user];
+        return [
+            'access_token' => $accessToken,
+            'refresh_token' => $refreshToken,
+            'user' => $user,
+            'device_id' => $deviceId,
+        ];
     }
 
     public function refresh(RefreshTokenDto $refreshToken): ?array
     {
-        $tokenModel = $this->personalAccessTokenRepository->findToken($refreshToken->token);
-        if (!$tokenModel || $tokenModel->abilities[0] !== 'refresh' || $tokenModel->expires_at < Carbon::now()) {
+        $tokenModel = $this->authRepository->findToken($refreshToken->token);
+
+        if (!$tokenModel || !in_array('refresh', $tokenModel->abilities) || $tokenModel->expires_at < now()) {
             return null;
         }
 
@@ -46,18 +47,23 @@ public function refresh(RefreshTokenDto $refreshToken): ?array
             return null;
         }
 
-        $user->tokens()->delete();
+        $deviceId = Str::afterLast($tokenModel->name, '_');
 
-        $accessToken = $user->createToken('access_token', ['access'], Carbon::now()->addMinutes(15))->plainTextToken;
-        $refreshToken = $user->createToken('refresh_token', ['refresh'], Carbon::now()->addDays(30))->plainTextToken;
+        $tokens = $this->authRepository->refreshToken($tokenModel, $user, $deviceId);
 
-        return ['access_token' => $accessToken, 'refresh_token' => $refreshToken, 'user' => $user];
+        return array_merge($tokens, [
+            'user' => $user,
+            'device_id' => $deviceId,
+        ]);
     }
 
-    public function logout(): void
+    public function logout(string $deviceId): void
     {
-        $user = Auth::user();
-        $userModel = $this->userRepository->find($user->id);
-        $userModel->tokens()->delete();
+        $user = auth()->user();
+        if (!$user) {
+            return;
+        }
+
+        $this->authRepository->deleteUserTokensByDevice($user, $deviceId);
     }
-}
+}

+ 0 - 9
database/seeders/UserTypePermissionSeeder.php

@@ -28,15 +28,6 @@ public function run(): void
                         $userTypePermission->save();
                     }
                     break;
-                case UserTypeSource::Guest:
-                    // Add relevant permissions for Guest
-                    break;
-                case UserTypeSource::RegularUser:
-                    // Add relevant permissions for RegularUser
-                    break;
-                case UserTypeSource::PayingUser:
-                    // Add relevant permissions for PayingUser
-                    break;
             }
         }
     }

+ 6 - 1
routes/console.php

@@ -2,7 +2,12 @@
 
 use Illuminate\Foundation\Inspiring;
 use Illuminate\Support\Facades\Artisan;
+use App\Console\Commands\CreateCrud;
 
 Artisan::command('inspire', function () {
     $this->comment(Inspiring::quote());
-})->purpose('Display an inspiring quote')->hourly();
+})->purpose('Display an inspiring quote');
+
+Artisan::command('create:crud {name}', function ($name) {
+    $this->call(CreateCrud::class, ['name' => $name]);
+})->purpose('Create a CRUD for a given model');

+ 21 - 0
storage/stubs/Collection.stub

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\ResourceCollection;
+
+class {{modelName}}Collection extends ResourceCollection
+{
+    /**
+     * Transform the resource collection into an array.
+     *
+     * @return array<int|string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return $this->collection->transform(function ($resource) {
+            return new {{modelName}}Resource($resource);
+        })->toArray();
+    }
+}

+ 47 - 0
storage/stubs/Controller.stub

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

+ 34 - 0
storage/stubs/Dto.stub

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\DataTransferObjects;
+
+use App\Http\Requests\{{modelName}}Request;
+
+class {{modelName}}Dto
+{
+    public function __construct(
+        // Add properties here
+    ) {
+    }
+
+    public static function fromRequest({{modelName}}Request $request): self
+    {
+        return new self(
+            // Initialize properties from $request
+        );
+    }
+
+    public static function fromArray(array $data): self
+    {
+        return new self(
+            // Initialize properties from $data
+        );
+    }
+
+    public function toArray(): array
+    {
+        return [
+            // Convert properties to array
+        ];
+    }
+}

+ 15 - 0
storage/stubs/Model.stub

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class {{modelName}} extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        // Add fillable attributes here
+    ];
+}

+ 42 - 0
storage/stubs/Repository.stub

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Repositories;
+
+use App\Models\{{modelName}};
+use App\DataTransferObjects\{{modelName}}Dto;
+use Illuminate\Database\Eloquent\Collection;
+
+class {{modelName}}Repository implements {{modelName}}RepositoryInterface
+{
+    public function __construct(
+        protected {{modelName}} $model
+    ){
+    }
+
+    public function all(): Collection
+    {
+        return $this->model->all();
+    }
+
+    public function find(int $id): ?{{modelName}}
+    {
+        return $this->model->find($id);
+    }
+
+    public function create({{modelName}}Dto $dto): {{modelName}}
+    {
+        return $this->model->create($dto->toArray());
+    }
+
+    public function update(int $id, {{modelName}}Dto $dto): {{modelName}}
+    {
+        $record = $this->find($id);
+        $record->update($dto->toArray());
+        return $record;
+    }
+
+    public function delete(int $id): bool
+    {
+        return $this->model->destroy($id) > 0;
+    }
+}

+ 20 - 0
storage/stubs/RepositoryInterface.stub

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Repositories;
+
+use App\DataTransferObjects\{{modelName}}Dto;
+use Illuminate\Database\Eloquent\Collection;
+use App\Models\{{modelName}};
+
+interface {{modelName}}RepositoryInterface
+{
+    public function all(): Collection;
+
+    public function find(int $id): ?{{modelName}};
+
+    public function create({{modelName}}Dto $dto): {{modelName}};
+
+    public function update(int $id, {{modelName}}Dto $dto): {{modelName}};
+
+    public function delete(int $id): bool;
+}

+ 15 - 0
storage/stubs/Request.stub

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class {{modelName}}Request extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            // Define validation rules here
+        ];
+    }
+}

+ 16 - 0
storage/stubs/Resource.stub

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class {{modelName}}Resource extends JsonResource
+{
+    public function toArray($request): array
+    {
+        return [
+            'id' => $this->id,
+            // Add other fields here
+        ];
+    }
+}

+ 41 - 0
storage/stubs/Service.stub

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