Crear una API RESTful con Laravel
Aprende a crear una API RESTful con Laravel
- URL Curso: https://www.udemy.com/course/aprende-a-crear-una-api-restful-con-laravel
- URL Repositorio API: https://github.com/coders-free/api.codersfree
- URL Repositorio Cliente: https://github.com/coders-free/cliente1
- URL Repositorio General: https://github.com/petrix12/apirestful_laravel8
Antes de iniciar:
- Crear proyecto en la página de GitHub con el nombre: apirestful_laravel8.
- Description: Proyecto para seguir el curso de Aprende a crear una API RESTful con Laravel, de Víctor Arana en Udemy
- Public.
- En la ubicación raíz del proyecto en la terminal de la máquina local:
- $ git init
- $ git add .
- $ git commit -m "Commit 00: Antes de iniciar"
- $ git branch -M main
- $ git remote add origin https://github.com/petrix12/apirestful_laravel8.git
- $ git push -u origin main
Sección 01: Introducción
Viedo 01. ¿Qué es una API RESTful?
- Contenido: explicación de una API RESTful.
- Commit Video 01:
- $ git add .
- $ git commit -m "Commit 01: ¿Qué es una API RESTful?"
- $ git push -u origin main
Viedo 02. Programas necesarios
- Programas requeridos:
- Otra opción podría ser Laragon ya que instala todos los programas mencionados anteriormente:
- Laragon
- Laragon Full (64-bit): Apache 2.4, Nginx, MySQL 5.7, PHP 7.4, Redis, Memcached, Node.js 14, npm, git, bitmana…
- Laragon
- Instalar el instalador de Laravel:
- $ composer global require laravel/installer
- Commit Video 02:
- $ git add .
- $ git commit -m "Commit 02: Programas necesarios"
- $ git push -u origin main
Viedo 03. Repositorio del curso
- Repositorio: api.codersfree: https://github.com/coders-free/api.codersfree
- Commit Nota 03:
- $ git add .
- $ git commit -m "Commit 03: Repositorio del curso"
- $ git push -u origin main
Sección 02: Configuración
Viedo 04. Creación del proyecto
- Crear proyecto para la API RESTful:
- $ laravel new api.codersfree
- Abrir el archivo: C:\Windows\System32\drivers\etc\hosts como administrador y en la parte final del archivo escribir.
127.0.0.1 api.codersfree.test
- Guardar y cerrar.
- Abri el archivo de texto plano de configuración de Apache C:\xampp\apache\conf\extra\httpd-vhosts.conf.
- Ir al final del archivo y anexar lo siguiente:
- Si nunca has creado un virtual host agregar:
<VirtualHost *> DocumentRoot "C:\xampp\htdocs" ServerName localhost </VirtualHost>
- Nota: Esta estructura se agrega una única vez.
- Luego agregar:
<VirtualHost *> DocumentRoot "C:\xampp\htdocs\cursos\24apirestful\api.codersfree\public" ServerName api.codersfree.test <Directory "C:\xampp\htdocs\cursos\24apirestful\api.codersfree\public"> Options All AllowOverride All Require all granted </Directory> </VirtualHost>
- Si nunca has creado un virtual host agregar:
- Guardar y cerrar.
- Reiniciar el servidor Apache.
- Nota 1: ahora podemos ejecutar nuestro proyecto local en el navegador introduciendo la siguiente dirección: http://api.codersfree.test
- Nota 2: En caso de que no funcione el enlace, cambiar en el archivo C:\xampp\apache\conf\extra\httpd-vhosts.conf todos los segmentos de código <VirtualHost *> por <VirtualHost *:80>.
- Commit Video 04:
- $ git add .
- $ git commit -m "Commit 04: Creación del proyecto"
- $ git push -u origin main
Viedo 05. Configurando archivo de rutas
- Abrir el proyecto api.codersfree.
- Modificar el método boot del provider api.codersfree\app\Providers\RouteServiceProvider.php:
public function boot() { $this->configureRateLimiting(); $this->routes(function () { Route::prefix('v1') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api-v1.php')); Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); }); }
- Renombrar el archivo de rutas api.codersfree\routes\api.php a api.codersfree\routes\api-v1.php.
- Commit Video 05:
- $ git add .
- $ git commit -m "Commit 05: Configurando archivo de rutas"
- $ git push -u origin main
Viedo 06. Registro de usuarios
- Abrir el proyecto api.codersfree.
- Eliminar la ruta auth:sanctum del archivo de rutas api.codersfree\routes\api-v1.php.
- Crear el controlador para registrar a los usuarios RegisterController:
- $ php artisan make:controller Api/RegisterController
- Crear ruta para el registro en el archivo de rutas api.codersfree\routes\api-v1.php:Importar la definición del controlador RegisterController:
Route::post('register', [RegisterController::class, 'store'])->name('api.v1.register');
use App\Http\Controllers\Api\RegisterController;
- Crear el método store en el controlador api.codersfree\app\Http\Controllers\Api\RegisterController.php:Importar la definición del controlador User:
public function store(Request $request){ $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:8|confirmed' ]); $user = User::create($request->all()); return response($user, 200); }
use App\Models\User;
- Crear la base de datos api.codersfree en nuestro adiministrador de bases de datos.
- Ejecutar las migraciones:
- $ php artisan migrate
- Realizar petición http para probar endpoint:
- Método: POST
- URL: http://api.codersfree.test/v1/register
- Body:
- Form:
- Field name: name | Value: Pedro Bazó
- Field name: email | Value: bazo.pedro@gmail.com
- Field name: password | Value: 12345678
- Field name: password_confirmation | Value: 12345678
- Form:
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe enviar el registro a la tabla users.
- Commit Video 06:
- $ git add .
- $ git commit -m "Commit 06: Registro de usuarios"
- $ git push -u origin main
Sección 3: Estructura del proyecto
Viedo 07. Maquetar la bbdd
- Crear un nuevo modelo y un nuevo diagrama para el proyecto api.restful en MySQL Workbench.
- Guardar el archivo como api.codersfree\api.restful.mwb.
- Crear la entidad categories con los campos:
- id
- name
- slug
- Crear la entidad posts con los campos:
- id
- name
- slug
- extract
- body
- status
- Crear la entidad users con los campos:
- id
- name
- password
- Crear la entidad tags con los campos:
- id
- name
- slug
- Generar relación 1:n entre categories y posts.
- Generar relación 1:n entre users y posts.
- Crear tabla post_tag para generar una relación de n:m entre posts y tags.
- Generar relación 1:n entre posts y post_tag.
- Generar relación 1:m entre tags y post_tag.
- Renombrar todas las llaves foráneas para seguir las convenciones de Laravel.
- Commit Video 07:
- $ git add .
- $ git commit -m "Commit 07: Registro de usuarios"
- $ git push -u origin main
Viedo 08. Crear el modelo físico
- Crear tabla image en el diagrama api.codersfree\api.restful.mwb con los campos:
- id
- url
- imageable_id
- imageable_type
- Abrir el proyecto api.codersfree.
- Crear el modelo Category con sus migraciones:
- $ php artisan make:model Category -m
- Modificar el método up de la migración api.codersfree\database\migrations\2021_09_18_202750_create_categories_table.php:
public function up() { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->timestamps(); }); }
- Crear el modelo Post con sus migraciones:
- $ php artisan make:model Post -m
- Modificar el método up de la migración api.codersfree\database\migrations\2021_09_18_221132_create_posts_table.php:Importar la definición del modelo Post:
public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->text('extract'); $table->longText('body'); $table->enum('status', [Post::BORRADOR, Post::PUBLICADO])->default(Post::BORRADOR); /* $table->unsignedBigInteger('category_id'); */ /* $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); */ // Esta instrucción equivale a las dos comentadas anteriormente // Ya que estamos siguiendo las convenciones de Laravel $table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->timestamps(); }); }
use App\Models\Post;
- Definir las constantes BORRADOR y PUBLICADO en el modelo api.codersfree\app\Models\Post.php:
≡ class Post extends Model { ≡ const BORRADOR = 1; const PUBLICADO = 2; }
- Crear el modelo Tag con sus migraciones:
- $ php artisan make:model Tag -m
- Modificar el método up de la migración api.codersfree\database\migrations\2021_09_18_221132_create_posts_table.php:
public function up() { Schema::create('tags', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->timestamps(); }); }
- Crear la migración para la tabla pivote (intermedia) post_tag:
- $ php artisan make:migration create_post_tag_table
- Modificar el método up de la migración api.codersfree\database\migrations\2021_09_18_223511_create_post_tag_table.php:
public function up() { Schema::create('post_tag', function (Blueprint $table) { $table->id(); $table->foreignId('post_id')->constrained()->onDelete('cascade'); $table->foreignId('tag_id')->constrained()->onDelete('cascade'); $table->timestamps(); }); }
- Crear el modelo Image con sus migraciones:
- $ php artisan make:model Image -m
- Modificar el método up de la migración api.codersfree\database\migrations\2021_09_18_223833_create_images_table.php:
public function up() { Schema::create('images', function (Blueprint $table) { $table->id(); $table->string('url'); /* $table->unsignedBigInteger('imageable_id'); */ /* $table->string('imageable_type'); */ // Esta instrucción equivale a las dos comentadas anteriormente // Ya que estamos siguiendo las convenciones de Laravel $table->morphs('imageable'); $table->timestamps(); }); }
- Reestablecer la base de datos api.codersfree:
- $ php artisan migrate:fresh
- Commit Video 08:
- $ git add .
- $ git commit -m "Commit 08: Crear el modelo físcio"
- $ git push -u origin main
Viedo 09. Generando relaciones
- Abrir el proyecto api.codersfree.
- Implementar relaciones en el modelo api.codersfree\app\Models\User.php:
≡ class User extends Authenticatable { ≡ // Relación 1:n entre **users** y **posts** public function posts(){ return $this->hasMany(Post::class); } }
- Implementar relaciones en el modelo api.codersfree\app\Models\Category.php:
≡ class Category extends Model { ≡ // Relación 1:n entre **categories** y **posts** public function posts(){ return $this->hasMany(Post::class); } }
- Implementar relaciones en el modelo api.codersfree\app\Models\Post.php:
≡ class Post extends Model { ≡ // Relación 1:n entre **users** y **posts** (inversa) public function user(){ return $this->belongsTo(User::class); } // Relación 1:n entre **categories** y **posts** (inversa) public function category(){ return $this->belongsTo(Category::class); } // Relación n:m entre **posts** y **tags** public function tags(){ return $this->belongsToMany(Tag::class); } // Relación 1:n polimorfica 1:n entre **posts** y **images** public function images(){ return $this->morphMany(Image::class, 'imageable'); } }
- Implementar relaciones en el modelo api.codersfree\app\Models\Tag.php:
≡ class Tag extends Model { ≡ // Relación n:m entre **tags** y **posts** public function posts(){ return $this->belongsToMany(Post::class); } }
- Implementar relaciones en el modelo api.codersfree\app\Models\Image.php:
≡ class Image extends Model { ≡ // Relación polimórfica entre **images** y otros modelos // El nombre de la función debe coincidir con el de su migración public function imageable(){ return $this->morphTo(); } }
- Commit Video 09:
- $ git add .
- $ git commit -m "Commit 09: Generando relaciones"
- $ git push -u origin main
Viedo 10. Introducir datos falsos
- Abrir el proyecto api.codersfree.
- Crear factory para los modelos Category, Post, Tag e Image:
- $ php artisan make:factory CategoryFactory
- $ php artisan make:factory PostFactory
- $ php artisan make:factory TagFactory
- $ php artisan make:factory ImageFactory
- Implementar el método definition del factory api.codersfree\database\factories\CategoryFactory.php:Importar la definición de Str:
public function definition() { $name = $this->faker->unique()->word(20); return [ 'name' => $name, 'slug' => Str::slug($name) ]; }
use Illuminate\Support\Str;
- Implementar el método definition del factory api.codersfree\database\factories\PostFactory.php:Importar las definiciones de Str y de los modelos Category y User:
public function definition() { $name = $this->faker->unique()->word(20); return [ 'name' => $name, 'slug' => Str::slug($name), 'extract' => $this->faker->text(250), 'body' => $this->faker->text(2000), 'status' => $this->faker->randomElement([Post::BORRADOR, Post::PUBLICADO]), 'category_id' => Category::all()->random()->id, 'user_id' => User::all()->random()->id ]; }
use App\Models\Category; use App\Models\User; use Illuminate\Support\Str;
- Implementar el método definition del factory api.codersfree\database\factories\TagFactory.php:Importar la definición de Str:
public function definition() { $name = $this->faker->unique()->word(20); return [ 'name' => $name, 'slug' => Str::slug($name) ]; }
use Illuminate\Support\Str;
- Implementar el método definition del factory api.codersfree\database\factories\ImageFactory.php:Importar la definición de Str:
public function definition() { return [ 'url' => 'posts/' . $this->faker->image('public/storage/posts', 640, 480, null, false) ]; }
use Illuminate\Support\Str;
- Modificar el valor de la siguiente variable de entorno del archivo api.codersfree\.env:
FILESYSTEM_DRIVER=public
- Generar acceso directo a api.codersfree\storage\app\public:
- $ php artisan storage:link
- Crear los seeders UserSeeder y PostSeeder:
- $ php artisan make:seeder UserSeeder
- $ php artisan make:seeder PostSeeder
- Implementar el método run del seeder api.codersfree\database\seeders\UserSeeder.php:Importar la definición del modelo User:
public function run() { $user = User::create([ 'name' => 'Pedro Bazó', 'email' => 'bazo.pedro@gmail.com', 'password' => bcrypt('12345678') ]); User::factory(99)->create(); }
use App\Models\User;
- Implementar el método run del seeder api.codersfree\database\seeders\PostSeeder.php:Importar la definición de los modelos Image y Post:
public function run() { Post::factory(100)->create()->each(function(Post $post){ Image::factory(4)->create([ 'imageable_id' => $post->id, 'imageable_type' => Post::class ]); $post->tags()->attach([ rand(1, 4), rand(5, 8) ]); }); }
use App\Models\Image; use App\Models\Post;
- Implementar el método run del seeder api.codersfree\database\seeders\DatabaseSeeder.php:Importar la definición de los modelos Category y Tag y el facade Storage:
public function run() { Storage::deleteDirectory('posts'); Storage::makeDirectory('posts'); $this->call(UserSeeder::class); Category::factory(4)->create(); Tag::factory(8)->create(); $this->call(PostSeeder::class); }
use App\Models\Category; use App\Models\Tag; use Illuminate\Support\Facades\Storage;
- Reestablecer la base de datos api.codersfree y ejecutar los seeders:
- $ php artisan migrate:fresh --seed
- Commit Video 10:
- $ git add .
- $ git commit -m "Video 10: Introducir datos falsos"
- $ git push -u origin main
Viedo 11. Solucionando posible error con faker
- Modificar el método image del provider api.codersfree\vendor\fakerphp\faker\src\Faker\Provider\Image.php:
public static function image( ≡ ) { ≡ if (function_exists('curl_exec')) { ≡ curl_setopt($ch, CURLOPT_FILE, $fp); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Nueva línea curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Nueva línea ≡ } elseif (ini_get('allow_url_fopen')) { ≡ } else { ≡ } ≡ }
- Commit Video 11:
- $ git add .
- $ git commit -m "Video 11: Solucionando posible error con faker"
- $ git push -u origin main
Viedo 12. Generando endpoints para categorias
- Crear controlador CategoryController con todos los métodos necesarios para administrarlo:
- $ php artisan make:controller Api\CategoryController --api --model=Category
- Modificar el archivo de rutas api.codersfree\routes\api-v1.php para administrar las rutas del modelo Category:Importar la definición del controlador CategoryController:
/* Route::get('categories', [CategoryController::class, 'index'])->name('api.v1.categories.index'); */ /* Route::post('categories', [CategoryController::class, 'store'])->name('api.v1.categories.store'); */ /* Route::get('categories/{category}', [CategoryController::class, 'show'])->name('api.v1.categories.show'); */ /* Route::put('categories/{category}', [CategoryController::class, 'update'])->name('api.v1.categories.update'); */ /* Route::delete('categories/{category}', [CategoryController::class, 'delete'])->name('api.v1.categories.delete'); */ // Esta isntrucción equivale a las 5 comentadas anteriormente Route::apiResource('categories', CategoryController::class)->names('api.v1.categories');
use App\Http\Controllers\Api\CategoryController;
- Commit Video 12:
- $ git add .
- $ git commit -m "Video 12: Generando endpoints para categorias"
- $ git push -u origin main
Sección 4: Query Scopes
Viedo 13. Recibir peticiones y generar respuestas para el recurso Category
- Abrir el proyecto api.codersfree.
- Implementar el método index del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function index() { $categories = Category::all(); return $categories; }
- Implementar el método store del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function store(Request $request) { $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:categories', ]); $category = Category::create($request->all()); return $category; }
- Habilitar la asignación masiva en el modelo api.codersfree\app\Models\Category.php:
≡ class Category extends Model { use HasFactory; protected $fillable = ['name', 'slug']; ≡ }
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar todos los registros a la tabla categories.
- Realizar petición http para probar endpoint:
- Método: POST
- URL: http://api.codersfree.test/v1/categories
- Body:
- Form:
- Field name: name | Value: Categoría de prueba
- Field name: slug | Value: categoria-de-prueba
- Form:
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe enviar el registro a la tabla categories.
- Implementar el método show del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function show(Category $category) { return $category; }
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories/5
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar el registro con id = 5 de la tabla categories.
- Implementar el método update del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function update(Request $request, Category $category) { $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:categories,slug,' . $category->id ]); $category->update($request->all()); return $category; }
- Realizar petición http para probar endpoint:
- Método: PUT
- URL: http://api.codersfree.test/v1/categories/5
- Body:
- Form-encode:
- Field name: name | Value: Categoría de prueba actualizada
- Field name: slug | Value: categoria-de-prueba-actualizada
- Form-encode:
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe actualizar el registro de la tabla categories con id = 5.
- Implementar el método destroy del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function destroy(Category $category) { $category->delete(); return $category; }
- Realizar petición http para probar endpoint:
- Método: DELETE
- URL: http://api.codersfree.test/v1/categories/5
- Header: Accept | Value: application/json
- Acción: Debe eliminar el registro de la tabla categories con id = 5.
- Commit Video 13:
- $ git add .
- $ git commit -m "Video 13: Recibir peticiones y generar respuestas para el recurso Category"
- $ git push -u origin main
Viedo 14. Incluir relaciones de los recursos
- Modificar el método show del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function show($id) { $category = Category::included()->findOrFail($id); // return CategoryResource::make($category); return $category; }
- Crear el método Query Scope scopeIncluded en el modelo api.codersfree\app\Models\Category.php:Definir variable allowIncluded en la clase Category:
public function scopeIncluded(Builder $query){ if (empty($this->allowIncluded) || empty(request('included'))) { return; } $relations = explode(',', request('included')); //['posts','relacion2'] $allowIncluded = collect($this->allowIncluded); foreach ($relations as $key => $relationship) { if (!$allowIncluded->contains($relationship)) { unset($relations[$key]); } } $query->with($relations); }
Importar la definición de la clase Builder:protected $allowIncluded = ['posts', 'posts.user'];
use Illuminate\Database\Eloquent\Builder;
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories/1?included=posts
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar el registro con id = 1 de la tabla categories y su relación con los registros de la tabla posts.
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories/1?included=posts.user
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar el registro con id = 1 de la tabla categories y su relación con los registros de la tabla posts y el usuario de la tabla users relacionado con el post.
- Commit Video 14:
- $ git add .
- $ git commit -m "Video 14: Incluir relaciones de los recursos"
- $ git push -u origin main
Viedo 15. Filtrar recursos
- Modificar el método index del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function index() { $categories = Category::included() ->filter() ->get(); return $categories; }
- Crear el método Query Scope scopeFilter en el modelo api.codersfree\app\Models\Category.php:Definir variable allowFilter en la clase Category:
public function scopeFilter(Builder $query){ if (empty($this->allowFilter) || empty(request('filter'))) { return; } $filters = request('filter'); $allowFilter = collect($this->allowFilter); foreach ($filters as $filter => $value) { if ($allowFilter->contains($filter)) { $query->where($filter, 'LIKE' , '%' . $value . '%'); } } }
protected $allowFilter = ['id', 'name', 'slug'];
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?filter[name]=ne&filter[slug]=e
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar los registro de la tabla categories que contengan en el campo name el texto 'ne' y en el campo slug la letra 'n'.
- Commit Video 15:
- $ git add .
- $ git commit -m "Video 15: Filtrar recursos"
- $ git push -u origin main
Viedo 16. Ordenar recursos
- Modificar el método index del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function index() { $categories = Category::included() ->filter() ->sort() ->get(); return $categories; }
- Crear el método Query Scope scopeSort en el modelo api.codersfree\app\Models\Category.php:Definir variable allowSort en la clase Category:
public function scopeSort(Builder $query){ if (empty($this->allowSort) || empty(request('sort'))) { return; } $sortFields = explode(',', request('sort')); $allowSort = collect($this->allowSort); foreach ($sortFields as $sortField) { $direction = 'asc'; if (substr($sortField, 0, 1) == '-') { $direction = 'desc'; $sortField = substr($sortField, 1); } if ($allowSort->contains($sortField)) { $query->orderBy($sortField, $direction); } } }
protected $allowSort = ['id', 'name', 'slug'];
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?sort=-name,id
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe ordenar los registro de la tabla categories por el campo name en forma descendente y luego por el campo id en forma ascendente.
- Commit Video 16:
- $ git add .
- $ git commit -m "Video 16: Ordenar recursos"
- $ git push -u origin main
Viedo 17. Paginar recursos
- Modificar el método index del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function index() { $categories = Category::included() ->filter() ->sort() ->getOrPaginate(); return $categories; }
- Crear el método Query Scope scopeGetOrPaginate en el modelo api.codersfree\app\Models\Category.php:
public function scopeGetOrPaginate(Builder $query){ if (request('perPage')) { $perPage = intval(request('perPage')); if ($perPage) { return $query->paginate($perPage); } } return $query->get(); }
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?perPage=3&page=2
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar los registro de la tabla categories paginados de 3 en 3 y posicionado en la página 2.
- Commit Video 17:
- $ git add .
- $ git commit -m "Video 17: Paginar recursos"
- $ git push -u origin main
Sección 5: Transformar respuestas
Viedo 18. Crear clase de recurso
- Crear el recurso CategoryResource:
- $ php artisan make:resource CategoryResource
- Modificar el método show del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:Importar la definición del recuros CategoryResource:
public function show($id) { $category = Category::included()->findOrFail($id); return CategoryResource::make($category); }
use App\Http\Resources\CategoryResource;
- Redefinir el método toArray del recurso api.codersfree\app\Http\Resources\CategoryResource.php:Importar la definición del recurso PostResource:
public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'slug' => $this->slug, 'posts' => PostResource::collection($this->whenLoaded('posts')) ]; }
use App\Http\Resources\PostResource;
- Redefinir el método index del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
public function index() { $categories = Category::included() ->filter() ->sort() ->getOrPaginate(); return CategoryResource::collection($categories); }
- Crear el recurso PostResource:
- $ php artisan make:resource PostResource
- Redefinir el método toArray del recurso api.codersfree\app\Http\Resources\PostResource.php:
public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'slug' => $this->slug, 'extract' => $this->extract, 'body' => $this->body, 'status' => $this->status == 1 ? 'BORRADOR' : 'PUBLICADO', 'user' => UserResource::make($this->whenLoaded('user')), 'category' => CategoryResource::make($this->whenLoaded('category')), ]; }
- Crear el recurso UserResource:
- $ php artisan make:resource UserResource
- Commit Video 18:
- $ git add .
- $ git commit -m "Video 18: Crear clase de recurso"
- $ git push -u origin main
Sección 6: Recurso Posts
Viedo 19. Ampliar la funcionalidad con los query scopes con traits de PHP
- Crear el trait api.codersfree\app\Traits\ApiTrait.php:
<?php namespace App\Traits; use Illuminate\Database\Eloquent\Builder; trait ApiTrait{ public function scopeIncluded(Builder $query){ if (empty($this->allowIncluded) || empty(request('included'))) { return; } $relations = explode(',', request('included')); //['posts','relacion2'] $allowIncluded = collect($this->allowIncluded); foreach ($relations as $key => $relationship) { if (!$allowIncluded->contains($relationship)) { unset($relations[$key]); } } $query->with($relations); } public function scopeFilter(Builder $query){ if (empty($this->allowFilter) || empty(request('filter'))) { return; } $filters = request('filter'); $allowFilter = collect($this->allowFilter); foreach ($filters as $filter => $value) { if ($allowFilter->contains($filter)) { $query->where($filter, 'LIKE' , '%' . $value . '%'); } } } public function scopeSort(Builder $query){ if (empty($this->allowSort) || empty(request('sort'))) { return; } $sortFields = explode(',', request('sort')); $allowSort = collect($this->allowSort); foreach ($sortFields as $sortField) { $direction = 'asc'; if (substr($sortField, 0, 1) == '-') { $direction = 'desc'; $sortField = substr($sortField, 1); } if ($allowSort->contains($sortField)) { $query->orderBy($sortField, $direction); } } } public function scopeGetOrPaginate(Builder $query){ if (request('perPage')) { $perPage = intval(request('perPage')); if ($perPage) { return $query->paginate($perPage); } } return $query->get(); } }
- Eliminar todos los scope del modelo api.codersfree\app\Models\Category.php y la definición de Builder, y en su lugar llamar al trait ApiTrait:
<?php namespace App\Models; use App\Traits\ApiTrait; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Category extends Model { use HasFactory, ApiTrait; protected $fillable = ['name', 'slug']; protected $allowIncluded = ['posts', 'posts.user']; protected $allowFilter = ['id', 'name', 'slug']; protected $allowSort = ['id', 'name', 'slug']; // Relación 1:n entre **categories** y **posts** public function posts(){ return $this->hasMany(Post::class); } }
- Importar la definición y el uso del trait ApiTrait en los modelos Image, Post, Tag y User:
<?php namespace App\Models; use App\Traits\ApiTrait; ≡ class {MODELO} extends Model { use ..., ApiTrait; ≡ }
- Modificar los métodos store, update y destroy del controlador api.codersfree\app\Http\Controllers\Api\CategoryController.php:
≡ class CategoryController extends Controller { ≡ public function store(Request $request) { $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:categories', ]); $category = Category::create($request->all()); return CategoryResource::make($category); } ≡ public function update(Request $request, Category $category) { $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:categories,slug,' . $category->id ]); $category->update($request->all()); return CategoryResource::make($category); } ≡ public function destroy(Category $category) { $category->delete(); return CategoryResource::make($category); } }
- Commit Video 19:
- $ git add .
- $ git commit -m "Video 19: Ampliar la funcionalidad con los query scopes con traits de PHP"
- $ git push -u origin main
Viedo 20. Recibir peticiones y generar respuestas para el recurso Post
- Crear el controlador PostController:
- $ php artisan make:controller Api\PostController --api --model=Post
- Crar las rutas para posts en el archivo de rutas api.codersfree\routes\api-v1.php:Importar la definición del controlador dddd:
Route::apiResource('posts', PostController::class)->names('api.v1.posts');
use App\Http\Controllers\Api\PostController;
- Definir el método index del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:Importar la definición del recurso PostResource:
public function index() { $posts = Post::included() ->filter() ->sort() ->getOrPaginate(); return PostResource::collection($posts); }
use App\Http\Resources\PostResource;
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/posts
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar los registro de la tabla posts.
- Definir el método store del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function store(Request $request) { $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:posts', 'extract' => 'required', 'body' => 'required', 'category_id' => 'required|exists:categories,id', 'user_id' => 'required|exists:users,id' ]); $post = Post::create($request->all()); return PostResource::make($post); }
- Habilitar asignación masiva en el modelo api.codersfree\app\Models\Post.php:
≡ class Post extends Model { ≡ const BORRADOR = 1; const PUBLICADO = 2; protected $fillable = ['name', 'slug', 'extract', 'body', 'status', 'category_id', 'user_id']; ≡ }
- Realizar petición http para probar endpoint:
- Método: POST
- URL: http://api.codersfree.test/v1/posts
- Body:
- Form:
- Field name: name | Value: Título de prueba
- Field name: slug | Value: titulo-de-prueba
- Field name: extract | Value: Cualquier cosa
- Field name: body | Value: Cualquier cosa
- Field name: category_id | Value: 1
- Field name: user_id | Value: 1
- Form:
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe registrar un post en la tabla posts.
- Definir el método show del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function show($id) { $post = Post::included()->findOrFail($id); return PostResource::make($post); }
- Realizar petición http para probar endpoint:
- Método: GET
- URL: http://api.codersfree.test/v1/posts/2
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe mostrar el registro de la tabla posts con id = 2.
- Definir el método update del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function update(Request $request, Post $post) { $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:posts,slug,' . $post->id, 'extract' => 'required', 'body' => 'required', 'category_id' => 'required|exists:categories,id', 'user_id' => 'required|exists:users,id' ]); $post->update($request->all()); return PostResource::make($post); }
- Definir el método destroy del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function destroy(Post $post) { $post->delete(); return PostResource::make($post); }
- Commit Video 20:
- $ git add .
- $ git commit -m "Video 20: Recibir peticiones y generar respuestas para el recurso Post"
- $ git push -u origin main
Sección 7: Laravel Passport
Viedo 21. Instalar Laravel Passport
- Redefinir el método store del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function store(Request $request) { $data = $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:posts', 'extract' => 'required', 'body' => 'required', 'category_id' => 'required|exists:categories,id' ]); $user = auth()->user(); $data['user_id'] = $user->id; $post = Post::create($data); return PostResource::make($post); }
- Crear el método constructo __construct en el controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function __construct(){ $this->middleware('auth:api')->except(['index', 'show']); }
- Instalar Laravel Passport: URL instalación: https://laravel.com/docs/8.x/passport
- $ composer require laravel/passport
- $ php artisan migrate
- $ php artisan passport:install --uuids
- In order to finish configuring client UUIDs, we need to rebuild the Passport database tables. Would you like to rollback and re-run your last migration? (yes/no) [no]: yes
- Recuperar:
Personal access client created successfully. Client ID: 94716146-8579-4e6f-afcc-29b2da6d125c Client secret: zR4WpKIHlUs7tWYDS9bo1zmIey0DxMgmSR3qslAk Password grant client created successfully. Client ID: 94716146-8d96-4e4f-9125-8e8a1d05ada0 Client secret: ySeMR1eQaPMLzU1ZcU5ivy9iqcEob3iTzqTC5Cvr
- $ php artisan migrate:fresh --seed
- Modificar el modelo api.codersfree\app\Models\User.php:
≡ //use Laravel\Sanctum\HasApiTokens; use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable, ApiTrait; ≡ }
- Modificar el provider api.codersfree\app\Providers\AuthServiceProvider.php:Nota: para ver todas las ruta generadas por passport:
≡ use Laravel\Passport\Passport; class AuthServiceProvider extends ServiceProvider { ≡ public function boot() { $this->registerPolicies(); Passport::routes(); } }
- $ php artisan r:l --name=passport
- Modificar el archivo de configuración api.codersfree\config\auth.php:
≡ 'guards' => [ ≡ 'api' => [ 'driver' => 'passport', 'provider' => 'users', 'hash' => false, ], ], ≡
- Commit Video 21:
- $ git add .
- $ git commit -m "Video 21: Instalar Laravel Passport"
- $ git push -u origin main
Viedo 22. Instalar Laravel Passport II
- Formas de obtener las llaves en producción:
- Ejecutar en producción:
- $ php artisan passport:keys
- Públicar el archivo de configuración de passport:
- $ php artisan vendor:publish --tag=passport-config
- Este comando creará el archivo de configuración api.codersfree\config\passport.php.
- Agragar las siguientes variables de entorno en api.codersfree\.env:
PASSPORT_PRIVATE_KEY="{Contenido del archivo: api.codersfree\storage\oauth-private.key}" PASSPORT_PUBLIC_KEY="{Contenido del archivo: api.codersfree\storage\oauth-public.key}"
- Ejecutar en producción:
- Commit Video 22:
- $ git add .
- $ git commit -m "Video 22: Instalar Laravel Passport II"
- $ git push -u origin main
Sección 8: Password grant client
Viedo 23. Solicitar un acces token desde postman
- Abrir proyecto api.codersfree.
- Crear cliente tipo password:
- $ php artisan passport:client --password
- What should we name the password grant client? [Laravel Password Grant Client]: > [Presionar ENTER]
- Which user provider should this client use to retrieve users? [users]: [0] users
[Presionar ENTER]
- Recuperar las credenciales:
Password grant client created successfully. Client ID: 94717435-7f73-4d1a-a9e2-0b88b0401377 Client secret: 5yo9JZN2W8kA9JVkvQE8KymeI48uBfm3F7ipLGXr
- $ php artisan passport:client --password
- Realizar petición http para probar endpoint:
- Método: POST
- URL: http://api.codersfree.test/oauth/token
- Body:
- Form:
- Field name: grant_type | Value: password
- Field name: client_id | Value: 94717435-7f73-4d1a-a9e2-0b88b0401377
- Field name: client_secret | Value: 5yo9JZN2W8kA9JVkvQE8KymeI48uBfm3F7ipLGXr
- Field name: username | Value: bazo.pedro@gmail.com
- Field name: password | Value: 12345678
- Form:
- Headers:
- Header: Accept | Value: application/json
- Acción: Obtiene un token para el usuario que corresponde con el campo email = bazo.pedro@gmail.com de la tabla users.
- Respuesta en formato JSON:
{ "token_type": "Bearer", "expires_in": 31536000, "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI5NDcxNzQzNS03ZjczLTRkMWEtYTllMi0wYjg4YjA0MDEzNzciLCJqdGkiOiJmMDBiZGYzOTFmNTIxMmU4ODUxNjQ0MGJlZGViNmYzMmE1NTVlN2JiMTBlZmY4ODcxZGNlZjkwYzllMjA4ZGFhODdiZGEyMzc0Y2RmZGJjYyIsImlhdCI6MTYzMjE1MTA5MS4xNDM2ODYsIm5iZiI6MTYzMjE1MTA5MS4xNDM2OTEsImV4cCI6MTY2MzY4NzA5MS4xMTUzNzMsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.vghu8oPHHRayzHvzdc5zoIxjnGcnbEx9Tj79Cff71fafTW2IMCN0D-p5hESciYCBe2DmgeAbj-DaQmflBSEe3ips9agV1SJ9Mlm81MfpqtQ5pDS9VndLTof4Wgf99vdPv82tqpXx5914fxEYrp6pB6mSfDkotzJFRFohGoyGXl8I7UIFWO7Bz6qr5I8amo_nqigxhLQKHH8fJb_UpwzqZlppIf-K28gWv_MaaBGIjRguFV3U8uRZ71IBc87r8hRU1ax98_9WoWv-ahN1dUQC2tCzZY2t4rR-cWo7Jatfzx6jN7_opOSKQs_FpBdq9vImz1i7FlooBbQRWhEVqED3LTjlj8vlTMOFnEvhhg5s8y2fwqcOhsWLW3uwLX47NLVvVE18VbGmeBACkwo6k8Oh7wDMqtSId_jp4ifpJHWYVKF_-Fj5Y-MgI7jBsoeZ1mDgZLVy6-v5XF4VMKGXHldZrwdtHolwZJpRuyWqiSvwlgFdmJegx7BTVqVOO37xG7E5uNC2NtR2Vs1_WlS0Eun49uqER__vqS_vHNYmEvm6cn6tsxJygC3E6-_Ux-TeOKwWsQAiRB01zyeM3EQ5cfWOqnMBPBa7xGFpQO0UwWX2CLVxmKDXiBkpTw8DVLGddvi5vDQ1b_VRtS635Nl2OPH6o9DCyXrH2vrDwSuLeYopceM", "refresh_token": "def50200465e686d738b5b2a9ecf1bbf855c44f33483b67f2fc612d19ed2eb45881bf852eb9e7745a942816a4151d76a637b7388f7af0658bd35fe0ef86c2ea55f13e5ea85df372c81014ae8416bcabb2ba909c250e578483c8e9f7b5505b6c7425d5fb16f4276c312b73d29e04e6e1a389296ea84070393f57727d76a1b67b0d81a2f81703e5da7cec4ef8393a194608e8a6c70c595f38b839dce8516f9d14c088b4d63e8aa6f90a215042d9ab358cd6dbb085914bbf357cb2e63bd9459f757e043a7e74b015ba33e785091490d2fa5053055e0887c5415579e132b6cd102a7fe42a5c2c619944b312843ccb9306351e361000b3007d3de043d701ada4de939417ef710ba2ea9ba01e1cb38c8756f6de461485d27c473aa783874db9aefc1e045200909bee41a74a276f22eb4a52c7a9d9dadd05d31fb9be4c214247c13d46d2b2f61f5265e46628469d171f6dedff4ba2948d2c24095807412b526bb7edd54819dd09556a2a9207278cba9b6768ff3027c33c8ebbb6a42664646eecca9276ba35d504f" }
- Realizar petición http para probar endpoint:
- Método: POST
- URL: http://api.codersfree.test/v1/posts
- Body:
- Form:
- Field name: name | Value: Título de prueba 2
- Field name: slug | Value: titulo-de-prueba-2
- Field name: extract | Value: Cualquier cosa
- Field name: body | Value: Cualquier cosa
- Field name: category_id | Value: 1
- Field name: user_id | Value: 1
- Form:
- Headers:
- Header: Accept | Value: application/json
- Header: Authorization | Value: Bearer + (un espacio) + (access_token de la petición anterior sin las comillas dobles)
- Acción: Obtenemos la autorización para registrar un post en la tabla posts.
- Commit Video 23:
- $ git add .
- $ git commit -m "Video 23: Solicitar un acces token desde postman"
- $ git push -u origin main
Viedo 24. Instalar laravel breeze en el cliente
- URL Repositorio Cliente: https://github.com/coders-free/cliente1
- Crear proyecto cliente para consumir la API RESTful:
- $ laravel new codersfree
- Abrir el archivo: C:\Windows\System32\drivers\etc\hosts como administrador y en la parte final del archivo escribir.
127.0.0.1 codersfree.test
- Guardar y cerrar.
- Abri el archivo de texto plano de configuración de Apache C:\xampp\apache\conf\extra\httpd-vhosts.conf.
- Ir al final del archivo y anexar lo siguiente:
<VirtualHost *> DocumentRoot "C:\xampp\htdocs\cursos\24apirestful\codersfree\public" ServerName codersfree.test <Directory "C:\xampp\htdocs\cursos\24apirestful\codersfree\public"> Options All AllowOverride All Require all granted </Directory> </VirtualHost>
- Guardar y cerrar.
- Reiniciar el servidor Apache.
- Nota 1: ahora podemos ejecutar nuestro proyecto local en el navegador introduciendo la siguiente dirección: http://codersfree.test
- Nota 2: En caso de que no funcione el enlace, cambiar en el archivo C:\xampp\apache\conf\extra\httpd-vhosts.conf todos los segmentos de código <VirtualHost *> por <VirtualHost *:80>.
- Crear base de datos codersfree.
- Modificar la siguiente variable de entorno del archivo codersfree\.env:
APP_NAME=Codersfree
- Instalara Laravel Breeze:
- URL Laravel Breeze: https://laravel.com/docs/8.x/starter-kits#:~:text=Laravel%20Breeze%20is%20a%20minimal,templates%20styled%20with%20Tailwind%20CSS.
- $ composer require laravel/breeze --dev
- $ php artisan breeze:install
- $ npm install
- $ npm run dev
- $ php artisan migrate
- Commit Video 24:
- $ git add .
- $ git commit -m "Video 24: Instalar laravel breeze en el cliente"
- $ git push -u origin main
Viedo 25. Crear endpoint para hacer login
- Abrir el proyecto api.codersfree.
- Crear controlador LoginController:
- php artisan make:controller Api\Auth\LoginController
- Crear ruta tipo post para login en api.codersfree\routes\api-v1.php:Importar la definición del controlador LoginController:
Route::post('login', [LoginController::class, 'store']);
use App\Http\Controllers\Api\Auth\LoginController;
- Crear método store en el controlador api.codersfree\app\Http\Controllers\Api\Auth\LoginController.php:Importar la definición de recurso UserResource, el modelo User y el facade Hash:
public function store(Request $request){ $request->validate([ 'email' => 'required|string|email', 'password' => 'required|string' ]); $user = User::where('email', $request->email)->firstOrFail(); if (Hash::check($request->password, $user->password)) { return UserResource::make($user); }else{ return response()->json(['message' => 'These credentials do not match our records.'], 404); } }
use App\Http\Resources\UserResource; use App\Models\User; use Illuminate\Support\Facades\Hash;
- Commit Video 25:
- $ git add .
- $ git commit -m "Video 25: Crear endpoint para hacer login"
- $ git push -u origin main
Viedo 26. Configurando el proyecto del cliente parahacer login
- Abrir el proyecto cliente codersfree:
- Eliminar los campos email_verified_at y password del archivo de migración codersfree\database\migrations\2014_10_12_000000_create_users_table.php:
public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->rememberToken(); $table->timestamps(); }); }
- Modificar el modelo codersfree\app\Models\User.php para eliminar las referencias al campo password:
≡ class User extends Authenticatable { ≡ protected $fillable = [ 'name', 'email', ]; ≡ protected $hidden = [ 'remember_token', ]; ≡ }
- Crear el modelo AccessToken con su migración:
- $ php artisan make:model AccessToken -m
- Modificar el método up de la migración codersfree\database\migrations\2021_09_20_195913_create_access_tokens_table.php:
public function up() { Schema::create('access_tokens', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); // id del usuario en el cliente $table->unsignedInteger('service_id'); // id del usuario en la API $table->text('access_token'); $table->text('refresh_token'); $table->dateTime('expires_at'); $table->timestamps(); }); }
- Habilitar la asignación masiva en el modelo codersfree\app\Models\AccessToken.php:
≡ class AccessToken extends Model { ≡ protected $fillable = ['user_id', 'service_id', 'access_token', 'refresh_token', 'expires_at']; }
- Establecer relación en el modelo codersfree\app\Models\User.php con el modelo AccessToken:
//Relacion 1:1 con el modelo AccessToken public function accessToken(){ return $this->hasOne(AccessToken::class); }
- Reestablecer la base de datos del cliente codersfree:
- $ php artisan migrate:fresh
- Commit Video 26:
- $ git add .
- $ git commit -m "Video 26: Configurando el proyecto del cliente parahacer login"
- $ git push -u origin main
Viedo 27. Iniciar sesión desde el cliente
- Abrir el proyecto cliente codersfree.
- Modificar el método store del controlador codersfree\app\Http\Controllers\Auth\AuthenticatedSessionController.php:Importar la definición del model User y del facade Http:
public function store(LoginRequest $request) { $request->validate([ 'email' => 'required|string|email', 'password' => 'required|string' ]); $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/v1/login', [ 'email' => $request->email, 'password' => $request->password ]); $service = $response->json(); $user = User::updateOrcreate([ 'email' => $request->email ], $service['data']); return $user; }
use App\Models\User; use Illuminate\Support\Facades\Http;
- Para solventar problemas al ejecutar más de un proyecto Laravel en el mismo servidor local, ejecutar en el proyecto cliente:
- $ php artisan config:cache
- Para probar la aplicación intentar acceder al endpoint de la aplicación API a través del login en la aplicación cliente:
- Email: bazo.pedro@gmail.com
- Password: 12345678
- Commit Video 27:
- $ git add .
- $ git commit -m "Video 27: Iniciar sesión desde el cliente"
- $ git push -u origin main
Viedo 28. Iniciar sesión desde el cliente II
- Abrir el proyecto cliente codersfree.
- Modificar el método store del controlador codersfree\app\Http\Controllers\Auth\AuthenticatedSessionController.php:
public function store(LoginRequest $request) { $request->validate([ 'email' => 'required|string|email', 'password' => 'required|string' ]); $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/v1/login', [ 'email' => $request->email, 'password' => $request->password ]); if ($response->status() == 404) { return back()->withErrors('These credentials do not match our records.'); } $service = $response->json(); $user = User::updateOrcreate([ 'email' => $request->email ], $service['data']); if (!$user->accessToken) { $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'password', 'client_id' => '94717435-7f73-4d1a-a9e2-0b88b0401377', 'client_secret' => '5yo9JZN2W8kA9JVkvQE8KymeI48uBfm3F7ipLGXr', 'username' => $request->email, 'password' => $request->password ]); $access_token = $response->json(); $user->accessToken()->create([ 'service_id' => $service['data']['id'], 'access_token' => $access_token['access_token'], 'refresh_token' => $access_token['refresh_token'], 'expires_at' => now()->addSecond($access_token['expires_in']) ]); } Auth::login($user, $request->remember); return redirect()->intended(RouteServiceProvider::HOME); }
- Commit Video 28:
- $ git add .
- $ git commit -m "Video 28: Iniciar sesión desde el cliente II"
- $ git push -u origin main
Viedo 29. Registrar usuario desde el cliente
- Abrir proyecto api.codersfree.
- Mover el controlador RegisterController.php:
- De: api.codersfree\app\Http\Controllers\Api
- A: api.codersfree\app\Http\Controllers\Api\Auth
- Modificar namespace del controlador api.codersfree\app\Http\Controllers\Api\Auth\RegisterController.php:
namespace App\Http\Controllers\Api\Auth;
- Cambiar la importación de la definición de RegisterController en el archivo de rutas api.codersfree\routes\api-v1.php:
- De:
use App\Http\Controllers\Api\RegisterController;
- A:
use App\Http\Controllers\Api\Auth\RegisterController;
- Modificar el método store del controlador api.codersfree\app\Http\Controllers\Api\Auth\RegisterController.php:Importar la definición del recurso UserResource:
public function store(Request $request){ $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:8|confirmed' ]); $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => bcrypt($request->password) ]); return UserResource::make($user); }
use App\Http\Resources\UserResource;
- Realizar petición http para probar endpoint:
- Método: POST
- URL: http://api.codersfree.test/v1/register
- Body:
- Form:
- Field name: name | Value: Fulanito De Tal
- Field name: email | Value: fulanito@gmail.com
- Field name: password | Value: 12345678
- Field name: password_confirmation | Value: 12345678
- Form:
- Headers:
- Header: Accept | Value: application/json
- Acción: Debe enviar el registro a la tabla users.
- Commit Video 29:
- $ git add .
- $ git commit -m "Video 29: Registrar usuario desde el cliente"
- $ git push -u origin main
Viedo 30. Registrar usuario desde el cliente II
- Abrir el proyecto cliente codersfree.
- Modificar el método store del controlador codersfree\app\Http\Controllers\Auth\RegisteredUserController.php:Importar la definición del facade Http:
public function store(Request $request) { $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => ['required', 'confirmed', Rules\Password::defaults()], ]); $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/v1/register', $request->all()); if ($response->status() == 422) { return back()->withErrors($response->json()['errors']); } $service = $response->json(); $user = User::create([ 'name' => $request->name, 'email' => $request->email ]); $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'password', 'client_id' => '94717435-7f73-4d1a-a9e2-0b88b0401377', 'client_secret' => '5yo9JZN2W8kA9JVkvQE8KymeI48uBfm3F7ipLGXr', 'username' => $request->email, 'password' => $request->password ]); $access_token = $response->json(); $user->accessToken()->create([ 'service_id' => $service['data']['id'], 'access_token' => $access_token['access_token'], 'refresh_token' => $access_token['refresh_token'], 'expires_at' => now()->addSecond($access_token['expires_in']) ]); event(new Registered($user)); Auth::login($user); return redirect(RouteServiceProvider::HOME); }
use Illuminate\Support\Facades\Http;
- Commit Video 30:
- $ git add .
- $ git commit -m "Video 30: Registrar usuario desde el cliente II"
- $ git push -u origin main
Viedo 31. Proteger credenciales
- Abrir el proyecto cliente codersfree.
- Crear servicio codersfree en el archivo de configuración codersfree\config\services.php:
return [ ≡ 'codersfree' => [ 'client_id' => env('CODERSFREE_CLIENT_ID'), 'client_secret' => env('CODERSFREE_CLIENT_SECRET') ], ];
- Crear variables de entorno para almacenar las llaves del cliente tipo password en codersfree\.env:
CODERSFREE_CLIENT_ID="94717435-7f73-4d1a-a9e2-0b88b0401377" CODERSFREE_CLIENT_SECRET="5yo9JZN2W8kA9JVkvQE8KymeI48uBfm3F7ipLGXr"
- Para cachear el servicio codersfree:
- $ php artisan config:clear
- $ php artisan config:cache
- Modificar la instrucción con la información de las llaves clientes tipo password en los controladores codersfree\app\Http\Controllers\Auth\RegisteredUserController.php y codersfree\app\Http\Controllers\Auth\AuthenticatedSessionController.php:
- Cambiar de:
'client_id' => '94717435-7f73-4d1a-a9e2-0b88b0401377', 'client_secret' => '5yo9JZN2W8kA9JVkvQE8KymeI48uBfm3F7ipLGXr',
- A:
'client_id' => config('services.codersfree.client_id'), 'client_secret' => config('services.codersfree.client_secret'),
- Commit Video 31:
- $ git add .
- $ git commit -m "Video 31: Proteger credenciales"
- $ git push -u origin main
Viedo 32. Trait para solicitar un acces token
- Abrir el proyecto cliente codersfree.
- Crear treit codersfree\app\Traits\token.php:
<?php namespace App\Traits; use Illuminate\Support\Facades\Http; trait Token{ public function setAccessToken($user, $service){ $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'password', 'client_id' => config('services.codersfree.client_id'), 'client_secret' => config('services.codersfree.client_secret'), 'username' => request('email'), 'password' => request('password'), ]); $access_token = $response->json(); $user->accessToken()->create([ 'service_id' => $service['data']['id'], 'access_token' => $access_token['access_token'], 'refresh_token' => $access_token['refresh_token'], 'expires_at' => now()->addSecond($access_token['expires_in']) ]); } }
- Importar y usar el trait Token a los controladores codersfree\app\Http\Controllers\Auth\RegisteredUserController.php y codersfree\app\Http\Controllers\Auth\AuthenticatedSessionController.php:
≡ use App\Traits\Token; class {Nombre del controlador} extends Controller { use Token; ≡ }
- Modificar el método store del controlador codersfree\app\Http\Controllers\Auth\RegisteredUserController.php:
public function store(Request $request) { ≡ $this->setAccessToken($user, $service); event(new Registered($user)); Auth::login($user); return redirect(RouteServiceProvider::HOME); }
- Modificar el método store del controlador codersfree\app\Http\Controllers\Auth\AuthenticatedSessionController.php:
public function store(LoginRequest $request) { ≡ if (!$user->accessToken) { $this->setAccessToken($user, $service); } Auth::login($user, $request->remember); return redirect()->intended(RouteServiceProvider::HOME); }
- Reestablecer la base de datos cliente codersfree:
- $ php artisan migrate:fresh
- Commit Video 32:
- $ git add .
- $ git commit -m "Video 32: Trait para solicitar un acces token"
- $ git push -u origin main
Viedo 33. Mandar acces token en las peticiones
- Abrir el proyecto client codersfree.
- Crear el controlador PostController:
- $ php artisan make:controller PostController
- Crear ruta get posts en el archivo de rutas codersfree\routes\web.php:Importar la definición del controlador PostController:
Route::get('posts', [PostController::class, 'store'])->middleware('auth');
use App\Http\Controllers\PostController;
- Crear método store en el controlador codersfree\app\Http\Controllers\PostController.php:Importar la definición del facade Http:
public function store(){ $response = Http::withHeaders([ 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . auth()->user()->accessToken->access_token ])->post('http://api.codersfree.test/v1/posts', [ 'name' => 'Este es un nombre de prueba', 'slug' => 'esto-esun-nombre-de-prueba', 'extract' => 'sdsdsds', 'body' => 'asdasdasdasdas', 'category_id' => 1 ]); return $response->json(); }
use Illuminate\Support\Facades\Http;
- Commit Video 33:
- $ git add .
- $ git commit -m "Video 33: Mandar acces token en las peticiones"
- $ git push -u origin main
Sección 9: Gran type refresh token
Viedo 34. Solicitar nuevo token
- Abrir el proyecto api.codersfree.
- Agregar el método tokensExpireIn de la clase Passport en el método boot del provider api.codersfree\app\Providers\AuthServiceProvider.php:
public function boot() { $this->registerPolicies(); Passport::routes(); Passport::tokensExpireIn(now()->addSeconds(60)); }
- Abrir el proyecto client codersfree.
- Modificar el método store del controlador codersfree\app\Http\Controllers\PostController.php:
public function store(){ $this->resolveAuthorization(); $response = Http::withHeaders([ 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . auth()->user()->accessToken->access_token ])->post('http://api.codersfree.test/v1/posts', [ 'name' => 'Este es un nombre de prueba', 'slug' => 'esto-esun-nombre-de-prueba', 'extract' => 'sdsdsds', 'body' => 'asdasdasdasdas', 'category_id' => 1 ]); return $response->json(); }
- Crear método resolveAuthorization en el controlador codersfree\app\Http\Controllers\Controller.php:Importar la definición del facade Http:
// Verifica si el token esta caducado para solicitar otro public function resolveAuthorization(){ if(auth()->user()->accessToken->expires_at <= now()){ $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'refresh_token', 'refresh_token' => auth()->user()->accessToken->refresh_token, 'client_id' => config('services.codersfree.client_id'), 'client_secret' => config('services.codersfree.client_secret') ]); $access_token = $response->json(); auth()->user()->accessToken->update([ 'access_token' => $access_token['access_token'], 'refresh_token' => $access_token['refresh_token'], 'expires_at' => now()->addSecond($access_token['expires_in']) ]); } }
use Illuminate\Support\Facades\Http;
- Commit Video 34:
- $ git add .
- $ git commit -m "Video 34: Solicitar nuevo token"
- $ git push -u origin main
Viedo 35. Purgar tokens
- Abrir el proyecto api.codersfree.
- Para purgar de la base de datos los access tokens caducos:
- $ php artisan passport:purge
- Programar tarea para purgar tokens caducos en el método schedule del kernel api.codersfree\app\Console\Kernel.php:
protected function schedule(Schedule $schedule) { // Para usar en desarrollo $schedule->command('passport:purge')->everyMinute(); // Para usar en producción // $schedule->command('passport:purge')->daily(); }
- Para que se ejecute el comando programado anteriormente cada minuto en nuestra máquina local:
- $ php artisan schedule:work
- Commit Video 35:
- $ git add .
- $ git commit -m "Video 35: Purgar tokens"
- $ git push -u origin main
Sección 10: Gran type authorization_code
Viedo 36. Instalar laravel breeze en nuestra api
URL Documentación Laravel Breeze: https://laravel.com/docs/8.x/starter-kits
- Abrir el proyecto api.codersfree.
- Instalara Laravel Breeze:
- $ composer require laravel/breeze --dev
- $ php artisan breeze:install
- $ npm install
- $ npm run dev
- $ php artisan migrate
- Crear controlador ClientController:
- $ php artisan make:controller ClientController
- Agregar ruta get clients en el archivo de rutas api.codersfree\routes\web.php:Importar la definción del controlador ClientController:
Route::get('clients', [ClientController::class, 'index'])->name('clients.index');
use App\Http\Controllers\ClientController;
- Crear el método index del controlador api.codersfree\app\Http\Controllers\ClientController.php:
public function index(){ return view('clients.index'); }
- Crear vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Clientes </h2> </x-slot> </x-app-layout>
- Agregar componente Clientes al dropdown de la plantilla api.codersfree\resources\views\layouts\navigation.blade.php:
≡ <!-- Settings Dropdown --> <div class="hidden sm:flex sm:items-center sm:ml-6"> <x-dropdown align="right" width="48"> ≡ <x-slot name="content"> {{-- Clientes --}} <x-dropdown-link :href="route('clients.index')"> Clientes </x-dropdown-link> <!-- Authentication --> ≡ </x-slot> </x-dropdown> </div>
- Commit Video 36:
- $ git add .
- $ git commit -m "Video 36: Instalar laravel breeze en nuestra api"
- $ git push -u origin main
Viedo 37. Crear formulario para crear nuevo cliente
- Abrir el proyecto api.codersfree.
- Crear el componente api.codersfree\resources\views\components\container.blade.php:
<div {{$attributes->merge(["class" => "max-w-7xl mx-auto sm:px-6 lg:px-8"])}}> {{$slot}} </div>
- Crear el componente api.codersfree\resources\views\components\form-section.blade.php:
<div {{$attributes->merge(["class" => "grid grid-cols-3 gap-6"])}}> <div class="px-4"> <h3 class="text-lg font-medium text-gray-900"> {{$title}} </h3> <p class="mt-1 text-sm text-gray-600"> {{$description}} </p> </div> <div class="col-span-2"> <div> <div class="px-4 py-5 sm:p-6 bg-white shadow sm:rounded-tl-md rounded-tr-md"> {{$slot}} </div> @isset($actions) <div class="px-6 py-3 bg-gray-100 shadow flex justify-end items-center rounded-bl-md rounded-br-md"> {{$actions}} </div> @endisset </div> </div> </div>
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Clientes </h2> </x-slot> <x-container class="py-8"> <x-form-section> <x-slot name="title"> Crea un nuevo cliente </x-slot> <x-slot name="description"> Ingrese los datos solicitados para poder crear un nuevo cliente </x-slot> <x-slot name="actions"> <x-button> Crear </x-button> </x-slot> </x-form-section> </x-container> </x-app-layout>
- Commit Video 37:
- $ git add .
- $ git commit -m "Video 37: Crear formulario para crear nuevo cliente"
- $ git push -u origin main
Viedo 38. Volver formulario responsivo
- Abrir el proyecto api.codersfree.
- Convertir en responsivo el componente api.codersfree\resources\views\components\form-section.blade.php:
<div {{$attributes->merge(["class" => "md:grid md:grid-cols-3 md:gap-6"])}}> <div class="px-4 sm:px-0"> <h3 class="text-lg font-medium text-gray-900"> {{$title}} </h3> <p class="mt-1 text-sm text-gray-600"> {{$description}} </p> </div> <div class="mt-5 md:mt-0 md:col-span-2"> <div> <div class="px-4 py-5 sm:p-6 bg-white shadow {{ isset($actions) ? 'sm:rounded-tl-md sm:rounded-tr-md' : 'sm:rounded-md' }}"> {{$slot}} </div> @isset($actions) <div class="px-6 py-3 bg-gray-100 shadow flex justify-end items-center sm:rounded-bl-md sm:rounded-br-md"> {{$actions}} </div> @endisset </div> </div> </div>
- Commit Video 38:
- $ git add .
- $ git commit -m "Video 38: Volver formulario responsivo"
- $ git push -u origin main
Viedo 39. Incluir vue y axios en nuestro proyecto
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Clientes </h2> </x-slot> <x-container class="py-8"> <x-form-section> <x-slot name="title"> Crea un nuevo cliente </x-slot> <x-slot name="description"> Ingrese los datos solicitados para poder crear un nuevo cliente </x-slot> <div class="grid grid-cols-6 gap-6"> <div class="col-span-6 sm:col-span-4"> <x-label>Nombre</x-label> <x-input type="text" class="w-full mt-1"></x-input> </div> <div class="col-span-6 sm:col-span-4"> <x-label>URL de redirección</x-label> <x-input type="text" class="w-full mt-1"></x-input> </div> </div> <x-slot name="actions"> <x-button> Crear </x-button> </x-slot> </x-form-section> </x-container> </x-app-layout>
- Agregar los CDN's de VUE, Axios y sweetalert2 en la plantilla api.codersfree\resources\views\layouts\app.blade.php para poder extender sus funcionalidades a todas las vistas:
≡ <head> ≡ <!-- VUE --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <!-- Axios --> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <!-- sweetalert2 --> <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script> </head> ≡
- URL CDN de VUE: https://vuejs.org/v2/guide/installation.html
- URL CDN de Axios: https://github.com/axios/axios
- URL CDN de sweetalert2: https://sweetalert2.github.io/#download
- Commit Video 39:
- $ git add .
- $ git commit -m "Video 39: Incluir vue y axios en nuestro proyecto"
- $ git push -u origin main
Viedo 40. Registrar nuevos clientes
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Clientes </h2> </x-slot> <x-container id="app" class="py-8"> <x-form-section> <x-slot name="title"> Crea un nuevo cliente </x-slot> <x-slot name="description"> Ingrese los datos solicitados para poder crear un nuevo cliente </x-slot> <div class="grid grid-cols-6 gap-6"> <div class="col-span-6 sm:col-span-4"> <x-label>Nombre</x-label> <x-input v-model="createForm.name" type="text" class="w-full mt-1"></x-input> </div> <div class="col-span-6 sm:col-span-4"> <x-label>URL de redirección</x-label> <x-input v-model="createForm.redirect" type="text" class="w-full mt-1"></x-input> </div> </div> <x-slot name="actions"> <x-button v-on:click="store"> Crear </x-button> </x-slot> </x-form-section> </x-container> @push('js') <script> new Vue({ el: "#app", data:{ createForm:{ errors: [], name: null, redirect: null, } }, methods:{ store(){ axios.post('/oauth/clients', this.createForm) .then(response => { this.createForm.name=null; this.createForm.redirect=null; Swal.fire( 'Deleted!', 'Your file has been deleted.', 'success' ) }).catch(error => { alert('No has completado los datos correspondientes') }) } } }); </script> @endpush </x-app-layout>
- Definir un stack en la plantilla api.codersfree\resources\views\layouts\app.blade.php:
≡ <body class="font-sans antialiased"> ≡ @stack('js') </body>
- Commit Video 40:
- $ git add .
- $ git commit -m "Video 40: Registrar nuevos clientes"
- $ git push -u origin main
Viedo 41. Mostrar listado de clientes
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> ≡ <x-container id="app" class="py-8"> <x-form-section class="mb-12"> ≡ </x-form-section> <x-form-section> <x-slot name="title"> Lista de clientes </x-slot> <x-slot name="description"> Aquí podrás encontrar todos los clientes que has agregado </x-slot> <div> <table class="text-gray-600"> <thead class="border-b border-gray-300"> <tr class="text-left"> <th class="py-2 w-full">Nombre</th> <th class="py-2">Acción</th> </tr> </thead> <tbody class="divide-y divide-gray-300"> <tr v-for="client in clients"> <td class=" y-2"> @{{ client.name }} {{-- El @ se escribe para evitar conflicto entre Blade y VUE --}} </td> <td class="flex divide-x divide-gray-300 py-2"> <a class="pr-2 hover:text-blue-600 font-semibold cursor-pointer">Editar</a> <a class="pl-2 hover:text-red-600 font-semibold cursor-pointer">Eliminar</a> </td> </tr> </tbody> </table> </div> </x-form-section> </x-container> @push('js') <script> new Vue({ el: "#app", data:{ clients: [], createForm:{ errors: [], name: null, redirect: null, } }, mounted(){ this.getClients(); }, methods:{ getClients(){ axios.get('/oauth/clients') .then(response =>{ this.clients = response.data }) }, store(){ axios.post('/oauth/clients', this.createForm) .then(response => { this.createForm.name=null; this.createForm.redirect=null; Swal.fire( 'Deleted!', 'Your file has been deleted.', 'success' ) }).catch(error => { alert('No has completado los datos correspondientes') }) } } }); </script> @endpush </x-app-layout>
- Commit Video 41:
- $ git add .
- $ git commit -m "Video 41: Mostrar listado de clientes"
- $ git push -u origin main
Viedo 42. Mostrar mensajes de error
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> ≡ <x-container id="app" class="py-8"> <x-form-section class="mb-12"> ≡ <div class="grid grid-cols-6 gap-6"> <div class="col-span-6 sm:col-span-4"> <div v-if="createForm.errors.length > 0"> <div class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 oy-3 rounded"> <strong class="font-bold">Whoops!</strong> <span>¡Algo salio mal!</span> <ul> <li v-for="error in createForm.errors"> @{{error}} </li> </ul> </div> </div> <x-label>Nombre</x-label> <x-input v-model="createForm.name" type="text" class="w-full mt-1"></x-input> </div> <div class="col-span-6 sm:col-span-4"> <x-label>URL de redirección</x-label> <x-input v-model="createForm.redirect" type="text" class="w-full mt-1"></x-input> </div> </div> <x-slot name="actions"> <x-button v-on:click="store" v-bind:disabled="createForm.disabled"> Crear </x-button> </x-slot> </x-form-section> <x-form-section v-if="clients.length > 0"> ≡ </x-form-section> </x-container> @push('js') <script> new Vue({ el: "#app", data:{ clients: [], createForm:{ disabled: false, errors: [], name: null, redirect: null, } }, mounted(){ this.getClients(); }, methods:{ getClients(){ axios.get('/oauth/clients') .then(response =>{ this.clients = response.data }) }, store(){ this.createForm.disabled = true; axios.post('/oauth/clients', this.createForm) .then(response => { this.createForm.name=null; this.createForm.redirect=null; Swal.fire( 'Creado con éxito!', 'El cliente se creó satisfactoriamente.', 'success' ) this.getClients(); this.createForm.disabled = false; }).catch(error => { this.createForm.errors = _.flatten(_.toArray(error.response.data.errors)); this.createForm.disabled = false; }) } } }); </script> @endpush </x-app-layout>
- Commit Video 42:
- $ git add .
- $ git commit -m "Video 42: Mostrar mensajes de error"
- $ git push -u origin main
Viedo 43. Traducir Laravel
- Abrir el proyecto api.codersfree.
- Ejecutar en la consola del proyecto:
- $ composer require laraveles/spanish
- $ php artisan vendor:publish --tag=lang
- Modificar el archivo de configuración api.codersfree\config\app.php:
<?php return [ ≡ 'locale' => 'es', ≡ ];
- Modificar el archivo api.codersfree\resources\lang\es\validation.php:
<?php return [ ≡ 'attributes' => [ 'name' => 'nombre', 'redirect' => 'redirección' ], ];
- Commit Video 43:
- $ git add .
- $ git commit -m "Video 43: Traducir Laravel"
- $ git push -u origin main
Viedo 44. Eliminar cliente
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> ≡ <x-container id="app" class="py-8"> ≡ <x-form-section v-if="clients.length > 0"> ≡ <div> <table class="text-gray-600"> ≡ <tbody class="divide-y divide-gray-300"> <tr v-for="client in clients"> ≡ <td class="flex divide-x divide-gray-300 py-2"> <a class="pr-2 hover:text-blue-600 font-semibold cursor-pointer">Editar</a> <a class="pl-2 hover:text-red-600 font-semibold cursor-pointer" v-on:click="destroy(client)">Eliminar</a> </td> </tr> </tbody> </table> </div> </x-form-section> </x-container> @push('js') <script> new Vue({ ≡ methods:{ ≡ destroy(client){ Swal.fire({ title: 'Are you sure?', text: "You won't be able to revert this!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Yes, delete it!' }).then((result) => { if (result.isConfirmed) { axios.delete('/oauth/clients/' + client.id) .then(response => { this.getClients(); }); Swal.fire( 'Deleted!', 'Your file has been deleted.', 'success' ) } }) } } }); </script> @endpush </x-app-layout>
- URL sweetalert2: https://sweetalert2.github.io
- Commit Video 44:
- $ git add .
- $ git commit -m "Video 44: Eliminar cliente"
- $ git push -u origin main
Viedo 45. Editar cliente I
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Clientes </h2> </x-slot> <div id="app" > <x-container class="py-8"> {{-- Crear cliente --}} <x-form-section class="mb-12"> <x-slot name="title"> Crea un nuevo cliente </x-slot> <x-slot name="description"> Ingrese los datos solicitados para poder crear un nuevo cliente </x-slot> <div class="grid grid-cols-6 gap-6"> <div class="col-span-6 sm:col-span-4"> <div v-if="createForm.errors.length > 0"> <div class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 oy-3 rounded"> <strong class="font-bold">Whoops!</strong> <span>¡Algo salio mal!</span> <ul> <li v-for="error in createForm.errors"> @{{error}} </li> </ul> </div> </div> <x-label>Nombre</x-label> <x-input v-model="createForm.name" type="text" class="w-full mt-1"></x-input> </div> <div class="col-span-6 sm:col-span-4"> <x-label>URL de redirección</x-label> <x-input v-model="createForm.redirect" type="text" class="w-full mt-1"></x-input> </div> </div> <x-slot name="actions"> <x-button v-on:click="store" v-bind:disabled="createForm.disabled"> Crear </x-button> </x-slot> </x-form-section> {{-- Mostrar clientes --}} <x-form-section v-if="clients.length > 0"> <x-slot name="title"> Lista de clientes </x-slot> <x-slot name="description"> Aquí podrás encontrar todos los clientes que has agregado </x-slot> <div> <table class="text-gray-600"> <thead class="border-b border-gray-300"> <tr class="text-left"> <th class="py-2 w-full">Nombre</th> <th class="py-2">Acción</th> </tr> </thead> <tbody class="divide-y divide-gray-300"> <tr v-for="client in clients"> <td class=" y-2"> @{{ client.name }} {{-- El @ se escribe para evitar conflicto entre Blade y VUE --}} </td> <td class="flex divide-x divide-gray-300 py-2"> <a class="pr-2 hover:text-blue-600 font-semibold cursor-pointer" v-on:click="edit(client)">Editar</a> <a class="pl-2 hover:text-red-600 font-semibold cursor-pointer" v-on:click="destroy(client)">Eliminar</a> </td> </tr> </tbody> </table> </div> </x-form-section> </x-container> {{-- Modal --}} <x-dialog-modal modal="editForm.open"> <x-slot name="title"> Editar cliente </x-slot> <x-slot name="content"> <div class="space-y-6"> <div v-if="editForm.errors.length > 0"> <div class="bg-red-100 border border-red-400 text-red-700 px-4 oy-3 rounded"> <strong class="font-bold">Whoops!</strong> <span>¡Algo salio mal!</span> <ul> <li v-for="error in editForm.errors"> @{{error}} </li> </ul> </div> </div> <div class=""> <x-label>Nombre</x-label> <x-input v-model="editForm.name" type="text" class="w-full mt-1"></x-input> </div> <div class=""> <x-label>URL de redirección</x-label> <x-input v-model="editForm.redirect" type="text" class="w-full mt-1"></x-input> </div> </div> </x-slot> <x-slot name="footer"> <button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> Actualizar </button> <button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> Cancelar </button> </x-slot> </x-dialog-modal> </div> @push('js') <script> new Vue({ el: "#app", data:{ clients: [], createForm:{ disabled: false, errors: [], name: null, redirect: null, }, editForm:{ open: false, disabled: false, errors: [], name: null, redirect: null, } }, mounted(){ this.getClients(); }, methods:{ getClients(){ axios.get('/oauth/clients') .then(response =>{ this.clients = response.data }) }, store(){ this.createForm.disabled = true; axios.post('/oauth/clients', this.createForm) .then(response => { this.createForm.name=null; this.createForm.redirect=null; this.createForm.errors=[]; Swal.fire( 'Creado con éxito!', 'El cliente se creó satisfactoriamente.', 'success' ) this.getClients(); this.createForm.disabled = false; }).catch(error => { this.createForm.errors = _.flatten(_.toArray(error.response.data.errors)); this.createForm.disabled = false; }) }, edit(client){ this.editForm.open = true; }, destroy(client){ Swal.fire({ title: 'Are you sure?', text: "You won't be able to revert this!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Yes, delete it!' }).then((result) => { if (result.isConfirmed) { axios.delete('/oauth/clients/' + client.id) .then(response => { this.getClients(); }); Swal.fire( 'Deleted!', 'Your file has been deleted.', 'success' ) } }) } } }); </script> @endpush </x-app-layout>
- Crear componente api.codersfree\resources\views\components\dialog-modal.blade.php:
@props(['modal']) <div v-show="{{$modal}}" class="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> <div v-on:click="{{$modal}} = false" class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div> <!-- This element is to trick the browser into centering the modal contents. --> <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> <div class="w-full inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="sm:flex sm:items-start"> <div class="w-full text-center sm:text-left"> <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title"> {{$title}} </h3> <div class="mt-2"> {{$content}} </div> </div> </div> </div> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> {{$footer}} </div> </div> </div> </div>
- URL Tailwind Modals: https://tailwindui.com/components/application-ui/overlays/modals
- Commit Video 45:
- $ git add .
- $ git commit -m "Video 45: Editar cliente I"
- $ git push -u origin main
Viedo 46. Editar cliente II
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> ≡ <div id="app" > <x-container class="py-8"> ≡ </x-container> {{-- Modal --}} <x-dialog-modal modal="editForm.open"> <x-slot name="title"> Editar cliente </x-slot> <x-slot name="content"> <div class="space-y-6"> <div v-if="editForm.errors.length > 0"> <div class="bg-red-100 border border-red-400 text-red-700 px-4 oy-3 rounded"> <strong class="font-bold">Whoops!</strong> <span>¡Algo salio mal!</span> <ul> <li v-for="error in editForm.errors"> @{{error}} </li> </ul> </div> </div> <div class=""> <x-label>Nombre</x-label> <x-input v-model="editForm.name" type="text" class="w-full mt-1"></x-input> </div> <div class=""> <x-label>URL de redirección</x-label> <x-input v-model="editForm.redirect" type="text" class="w-full mt-1"></x-input> </div> </div> </x-slot> <x-slot name="footer"> <button type="button" v-on:click="update()" v-bind:disabled="editForm.disabled" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"> Actualizar </button> <button v-on:click="editForm.open = false" type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> Cancelar </button> </x-slot> </x-dialog-modal> </div> @push('js') <script> new Vue({ el: "#app", data:{ clients: [], createForm:{ ≡ }, editForm:{ open: false, disabled: false, errors: [], id: null, name: null, redirect: null, } }, mounted(){ this.getClients(); }, methods:{ ≡ store(){ ≡ }, edit(client){ this.editForm.open = true; this.editForm.errors = []; this.editForm.id = client.id; this.editForm.name = client.name; this.editForm.redirect = client.redirect; }, update(){ this.editForm.disabled = true; axios.put('/oauth/clients/' + this.editForm.id, this.editForm) .then(response => { this.editForm.open = false; this.editForm.disabled = false; this.editForm.name = null; this.editForm.redirect = null; this.editForm.errors = []; Swal.fire( '¡Actualizado con éxito!', 'El cliente se actualizó satisfactoriamente.', 'success' ); this.getClients(); }).catch(error => { this.editForm.errors = _.flatten(_.toArray(error.response.data.errors)); this.editForm.disabled = false; }) }, destroy(client){ ≡ } } }); </script> @endpush </x-app-layout>
- Commit Video 46:
- $ git add .
- $ git commit -m "Video 46: Editar cliente II"
- $ git push -u origin main
Viedo 47. Credenciales del cliente
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\clients\index.blade.php:
<x-app-layout> ≡ <div id="app" > <x-container class="py-8"> {{-- Crear cliente --}} <x-form-section class="mb-12"> ≡ </x-form-section> {{-- Mostrar clientes --}} <x-form-section v-if="clients.length > 0"> ≡ </x-form-section> </x-container> {{-- Modal edit --}} <x-dialog-modal modal="editForm.open"> ≡ </x-dialog-modal> {{-- Modal show --}} <x-dialog-modal modal="showClient.open"> <x-slot name="title"> Mostrar credenciales </x-slot> <x-slot name="content"> <div class="space-y-2"> <p> <span class="font-semibold">CLIENTE:</span> <span v-text="showClient.name"></span> </p> <p> <span class="font-semibold">CLIENTE_ID:</span> <span v-text="showClient.id"></span> </p> <p> <span class="font-semibold">CLIENTE_SECRET:</span> <span v-text="showClient.secret"></span> </p> </div> </x-slot> <x-slot name="footer"> <button v-on:click="showClient.open = false" type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"> Cancelar </button> </x-slot> </x-dialog-modal> </div> @push('js') <script> new Vue({ el: "#app", data:{ clients: [], showClient:{ open: false, name: null, id: null, secret: null, }, ≡ }, mounted(){ this.getClients(); }, methods:{ getClients(){ ≡ }, show(client){ this.showClient.open = true; this.showClient.name = client.name; this.showClient.id = client.id; this.showClient.secret = client.secret; }, store(){ this.createForm.disabled = true; axios.post('/oauth/clients', this.createForm) .then(response => { this.createForm.name=null; this.createForm.redirect=null; this.createForm.errors=[]; /* Swal.fire( 'Creado con éxito!', 'El cliente se creó satisfactoriamente.', 'success' ) */ this.show(response.data); this.getClients(); this.createForm.disabled = false; }).catch(error => { this.createForm.errors = _.flatten(_.toArray(error.response.data.errors)); this.createForm.disabled = false; }) }, ≡ } }); </script> @endpush </x-app-layout>
- Commit Video 47:
- $ git add .
- $ git commit -m "Video 47: Credenciales del cliente"
- $ git push -u origin main
Viedo 48. Crear nuevo proyecto para un cliente externo
- Crear nuevo proyecto Laravel:
- $ laravel new cliente2
- Abrir el archivo: C:\Windows\System32\drivers\etc\hosts como administrador y en la parte final del archivo escribir.
127.0.0.1 cliente2.test
- Guardar y cerrar.
- Abri el archivo de texto plano de configuración de Apache C:\xampp\apache\conf\extra\httpd-vhosts.conf.
- Ir al final del archivo y anexar lo siguiente:
<VirtualHost *> DocumentRoot "C:\xampp\htdocs\cursos\24apirestful\cliente2\public" ServerName cliente2.test <Directory "C:\xampp\htdocs\cursos\24apirestful\cliente2\public"> Options All AllowOverride All Require all granted </Directory> </VirtualHost>
- Guardar y cerrar.
- Reiniciar el servidor Apache.
- Nota 1: ahora podemos ejecutar nuestro proyecto local en el navegador introduciendo la siguiente dirección: http://cliente2.test
- Nota 2: En caso de que no funcione el enlace, cambiar en el archivo C:\xampp\apache\conf\extra\httpd-vhosts.conf todos los segmentos de código <VirtualHost *> por <VirtualHost *:80>.
- Crear el servicio codersfree en cliente2\config\services.php:
<?php return [ ≡ 'codersfree' => [ 'client_id' => env('CODERSFREE_CLIENT_ID'), 'client_secret' => env('CODERSFREE_CLIENT_SECRET') ], ];
- Definir las variables del servicio codersfree al final del archivo de variables de entorno cliente2\.env:
CODERSFREE_CLIENT_ID= CODERSFREE_CLIENT_SECRET=
- Abrir el proyecto api.codersfree y crear el cliente:
- Nombre: Cliente2
- URL de redirección: http://cliente2.test/callback
- Copiar las credenciales de Cliente2 y volver al nuevo proyecto cliente2.
- Pegar las credenciales que acabamos de copiar en las correspondiente al servicio codersfree en cliente2\.env:
CODERSFREE_CLIENT_ID=94753fa7-3c7a-4aa6-a71a-5ec8bf202215 CODERSFREE_CLIENT_SECRET=KL61T3ypp5qjE6iqaSFb2Vixvt1CmzhocuygziyS
- Para solventar problemas al ejecutar más de un proyecto Laravel en el mismo servidor local, ejecutar en el proyecto cliente2:
- $ php artisan config:cache
- Commit Video 48:
- $ git add .
- $ git commit -m "Video 48: Crear nuevo proyecto para un cliente externo"
- $ git push -u origin main
Viedo 49. Instalar laravel breeze en el cliente 2
- Abrir el proyecto cliente2.
- Crear base de datos cliente2.
- Modificar la siguiente variable de entorno del archivo cliente2\.env:
APP_NAME=Cliente2
- Instalara Laravel Breeze: URL Laravel Breeze: https://laravel.com/docs/8.x/starter-kits#:~:text=Laravel%20Breeze%20is%20a%20minimal,templates%20styled%20with%20Tailwind%20CSS.
- $ composer require laravel/breeze --dev
- $ php artisan breeze:install
- $ npm install
- $ npm run dev
- $ php artisan migrate
- Modificar la vista cliente2\resources\views\dashboard.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Dashboard') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> Solicitar permisos <a href="{{route('redirect')}}" class="ml-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Redirigir </a> </div> </div> </div> </div> </x-app-layout>
- URL Buttons Tailwind: https://v1.tailwindcss.com/components/buttons
- Crear ruta redirect tipo get en el archivo de rutas cliente2\routes\web.php:
Route::get('redirect', function () { return "Te has redirigido"; })->name('redirect');
- Commit Video 49:
- $ git add .
- $ git commit -m "Video 49: Instalar laravel breeze en el cliente 2"
- $ git push -u origin main
Viedo 50. Obtener código de autorización
URL Documentación Laravel Passport: https://laravel.com/docs/8.x/passport
- Abrir el proyecto cliente2.
- Crear controlador OauthController:
- $ php artisan make:controller OauthController
- Modificar la ruta redirect tipo get en el archivo de rutas cliente2\routes\web.php:Importar la definición del controlador OauthController:
Route::get('redirect', [OauthController::class, 'redirect'])->name('redirect');
use App\Http\Controllers\OauthController;
- Definir el método redirect en el controlador cliente2\app\Http\Controllers\OauthController.php:Importar la definición del soporte Str:
public function redirect(Request $request){ $request->session()->put('state', $state = Str::random(40)); $query = http_build_query([ 'client_id' => config('services.codersfree.client_id'), 'redirect_uri' => route('callback'), 'response_type' => 'code', 'scope' => '', 'state' => $state, ]); return redirect('http://api.codersfree.test/oauth/authorize?'.$query); }
use Illuminate\Support\Str;
- Crear ruta callback tipo get en el archivo de rutas cliente2\routes\web.php:
Route::get('callback', [OauthController::class, 'callback'])->name('callback');
- Definir el método callback en el controlador cliente2\app\Http\Controllers\OauthController.php:
public function callback(Request $request){ return $request->all(); }
- Abrir el proyecto api.codersfree.
- Publicar vistas de Laravel Passport:
- $ php artisan vendor:publish --tag=passport-views
- Modificar la vista api.codersfree\resources\views\vendor\passport\authorize.blade.php:
<!DOCTYPE html> <html lang="en"> <head> ≡ <title>{{ config('app.name') }} - Authorization</title> <!-- Styles --> {{-- <link href="{{ asset('/css/app.css') }}" rel="stylesheet"> --}} <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous"> <style> ≡ </style> </head> <body class="passport-authorize"> ≡ </body> </html>
- URL CDN Bootstrap: https://getbootstrap.com
- Commit Video 50:
- $ git add .
- $ git commit -m "Video 50: Obtener código de autorización"
- $ git push -u origin main
Viedo 51. Solicitar Acees Token
- Abrir el proyecto cliente2.
- Redifinir el método callback en el controlador cliente2\app\Http\Controllers\OauthController.php:Importar la definición del facade Http:
public function callback(Request $request){ $state = $request->session()->pull('state'); throw_unless( strlen($state) > 0 && $state === $request->state, InvalidArgumentException::class ); $response = Http::asForm()->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'authorization_code', 'client_id' => config('services.codersfree.client_id'), 'client_secret' => config('services.codersfree.client_secret'), 'redirect_uri' => route('callback'), 'code' => $request->code, ]); return $response->json(); }
use Illuminate\Support\Facades\Http;
- URL Documentación Laravel Passport: https://laravel.com/docs/8.x/passport
- Commit Video 51:
- $ git add .
- $ git commit -m "Video 51: Solicitar Acees Token"
- $ git push -u origin main
Sección 11: Personal Access Token
Viedo 52. Crear ruta
- Abrir el proyecto api.codersfree.
- Crear el controlador TokenController:
- $ php artisan make:controller TokenController
- Crear ruta get api-tokens en api.codersfree\routes\web.php:Importar el controlador TokenController:
Route::get('api-tokens', [TokenController::class, 'index'])->name('tokens.index');
use App\Http\Controllers\TokenController;
- Crear el método index en el controlador api.codersfree\app\Http\Controllers\TokenController.php:
public function index(){ return view('tokens.index'); }
- Crear vista api.codersfree\resources\views\tokens\index.blade.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Api Tokens </h2> </x-slot> </x-app-layout>
- Agregar el menú Api Tokens en la plantilla api.codersfree\resources\views\layouts\navigation.blade.php:
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100"> <!-- Primary Navigation Menu --> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-16"> ≡ <!-- Settings Dropdown --> <div class="hidden sm:flex sm:items-center sm:ml-6"> <x-dropdown align="right" width="48"> ≡ <x-slot name="content"> {{-- Clientes --}} <x-dropdown-link :href="route('clients.index')"> Clientes </x-dropdown-link> {{-- Api Tokens --}} <x-dropdown-link :href="route('tokens.index')"> Api Tokens </x-dropdown-link> <!-- Authentication --> ≡ </x-slot> </x-dropdown> </div> <!-- Hamburger --> ≡ </div> </div> <!-- Responsive Navigation Menu --> <div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden"> ≡ <!-- Responsive Settings Options --> <div class="pt-4 pb-1 border-t border-gray-200"> ≡ <div class="mt-3 space-y-1"> {{-- Clientes --}} <x-responsive-nav-link :href="route('clients.index')"> Clientes </x-responsive-nav-link> {{-- Api Tokens --}} <x-responsive-nav-link :href="route('tokens.index')"> Api Tokens </x-responsive-nav-link> <!-- Authentication --> ≡ </div> </div> </div> </nav>
- Commit Video 52:
- $ git add .
- $ git commit -m "Video 52: Crear ruta"
- $ git push -u origin main
Viedo 53. Generar Access Token
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\tokens\index.blade.php:
<x-app-layout> ≡ <div id="app"> <x-container class="py-8"> <x-form-section class="mb-12"> <x-slot name="title"> Access Token </x-slot> <x-slot name="description"> Aquí podrás generar un Access Token </x-slot> <div class="grid grid-cols-6 gap-6"> <div class="col-span-6 sm:cok-span-4"> <div v-if="form.errors.length > 0" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded"> <strong class="font-bold">Whoops!</strong> <span>¡Algo salio mal!</span> <ul> <li v-for="error in form.errors"> @{{ error }} </li> </ul> </div> <x-label> Nombre </x-label> <x-input v-model="form.name" type="text" class="w-full mt-1"/> </div> </div> <x-slot name="actions"> <x-button v-on:click="store" v-bind:disabled="form.disabled"> Crear </x-button> </x-slot> </x-form-section> </x-container> </div> @push('js') <script> new Vue({ el: "#app", data: { form: { name: '', errors: [], disabled: false, }, }, methods: { store(){ this.form.disabled = true; axios.post('/oauth/personal-access-tokens', this.form) .then(response => { this.form.name = ''; this.form.errors = []; this.form.disabled = false; }).catch(error => { this.form.errors = _.flatten(_.toArray(error.response.data.errors)); this.form.disabled = false; }) } }, }); </script> @endpush </x-app-layout>
- Generar un cliente de tipo personal:
- $ php artisan passport:client --personal
- What should we name the personal access client? [Laravel Personal Access Client]: (ENTER)
- Recuperar las credenciales:
Personal access client created successfully. Client ID: 94774625-6389-4b9d-9889-bc36d32fe1f8 Client secret: rL74WnIH8UX7YUzmEZbPe6nu1B9eWyu4Cj6Kj2iE
- Nota: No es estrictamente necesario almacenar estas credenciales, ya que el proyecto las tomará por defecto.
- $ php artisan passport:client --personal
- Commit Video 53:
- $ git add .
- $ git commit -m "Video 53: Generar Access Token"
- $ git push -u origin main
Viedo 54. Mostrar Access Token
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\tokens\index.blade.php:
<x-app-layout> ≡ <div id="app"> <x-container class="py-8"> {{-- Crear Access Token --}} <x-form-section class="mb-12"> ≡ </x-form-section> {{-- Mostrar Access Token --}} <x-form-section v-if="tokens.length > 0"> <x-slot name="title"> Lista de Access Token </x-slot> <x-slot name="description"> Aquí podrás encontrar todos los Access Token creados </x-slot> <div> <table class="text-gray-600"> <thead class="border-b border-gray-300"> <tr class="text-left"> <th class="py-2 w-full">Nombre</th> <th class="py-2">Acción</th> </tr> </thead> <tbody class="divide-y divide-gray-300"> <tr v-for="token in tokens"> <td class="py-2"> @{{ token.name }} </td> <td class="flex divide-x divide-gray-300 py-2"> <a v-on:click="{{-- show(token) --}}" class="pr-2 hover:text-green-600 font-semibold cursor-pointer"> Ver </a> <a v-on:click="{{-- edit(token) --}}" class="px-2 hover:text-blue-600 font-semibold cursor-pointer"> Editar </a> <a class="pl-2 hover:text-red-600 font-semibold cursor-pointer" v-on:click="revoke(token)"> Eliminar </a> </td> </tr> </tbody> </table> </div> </x-form-section> </x-container> </div> @push('js') <script> new Vue({ el: "#app", data: { tokens: [], form: { name: '', errors: [], disabled: false, }, }, mounted(){ this.getTokens(); }, methods: { getTokens(){ ≡ }, store(){ this.form.disabled = true; axios.post('/oauth/personal-access-tokens', this.form) .then(response => { this.form.name = ''; this.form.errors = []; this.form.disabled = false; this.getTokens(); }).catch(error => { this.form.errors = _.flatten(_.toArray(error.response.data.errors)); this.form.disabled = false; }) }, revoke(token){ Swal.fire({ title: 'Are you sure?', text: "You won't be able to revert this!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Yes, delete it!' }).then((result) => { if (result.isConfirmed) { axios.delete('/oauth/personal-access-tokens/' + token.id) .then(response => { this.getTokens(); }); Swal.fire( 'Deleted!', 'Your file has been deleted.', 'success' ) } }) }, }, }); </script> @endpush </x-app-layout>
- Commit Video 54:
- $ git add .
- $ git commit -m "Video 54: Mostrar Access Token"
- $ git push -u origin main
Viedo 55. Mostrar Acces token II
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\tokens\index.blade.php:
<x-app-layout> ≡ <div id="app"> <x-container class="py-8"> {{-- Crear Access Token --}} <x-form-section class="mb-12"> ≡ </x-form-section> {{-- Mostrar Access Token --}} <x-form-section v-if="tokens.length > 0"> <x-slot name="title"> Lista de Access Token </x-slot> <x-slot name="description"> Aquí podrás encontrar todos los Access Token creados </x-slot> <div> <table class="text-gray-600"> <thead class="border-b border-gray-300"> <tr class="text-left"> <th class="py-2 w-full">Nombre</th> <th class="py-2">Acción</th> </tr> </thead> <tbody class="divide-y divide-gray-300"> <tr v-for="token in tokens"> <td class="py-2"> @{{ token.name }} </td> <td class="flex divide-x divide-gray-300 py-2"> <a v-on:click="show(token)" class="pr-2 hover:text-green-600 font-semibold cursor-pointer"> Ver </a> <a class="pl-2 hover:text-red-600 font-semibold cursor-pointer" v-on:click="revoke(token)"> Eliminar </a> </td> </tr> </tbody> </table> </div> </x-form-section> </x-container> {{-- Modal show --}} <x-dialog-modal modal="showToken.open"> <x-slot name="title"> Mostrar access token </x-slot> <x-slot name="content"> <div class="space-y-2 overflow-auto"> <p> <span class="font-semibold">Access Token: </span> <span v-text="showToken.id"></span> </p> </div> </x-slot> <x-slot name="footer"> <button v-on:click="showToken.open = false" type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"> Cancelar </button> </x-slot> </x-dialog-modal> </div> @push('js') <script> new Vue({ el: "#app", data: { ≡ showToken: { open: false, id: '' } }, mounted(){ this.getTokens(); }, methods: { getTokens(){ ≡ }, show(token){ this.showToken.open = true; this.showToken.id = token.id; }, store(){ ≡ }, revoke(token){ ≡ }, }, }); </script> @endpush </x-app-layout>
- Commit Video 55:
- $ git add .
- $ git commit -m "Video 55: Mostrar Acces token II"
- $ git push -u origin main
Sección 12: Scopes
Viedo 56. Proteger rutas por scopes
- Abrir el proyecto api.codersfree.
- Modificar el método boot del provider api.codersfree\app\Providers\AuthServiceProvider.php:
public function boot() { ≡ // Establecer los permisos de los tokens Passport::tokensCan([ 'create-post' => 'Crear un post', 'read-post' => 'Leer un post', 'update-post' => 'Actualziar un post', 'delete-post' => 'Eliminar un post' ]); // Permitir lectura a los post por defecto en todos los permisos Passport::setDefaultScope([ 'read-post' ]); }
- Con la intención de proteger las rutas, registrar los middelware de Laravel Passport en el kernel api.codersfree\app\Http\Kernel.php:
≡ use Laravel\Passport\Http\Middleware\CheckScopes; use Laravel\Passport\Http\Middleware\CheckForAnyScope; class Kernel extends HttpKernel { ≡ protected $routeMiddleware = [ ≡ 'scopes' => CheckScopes::class, 'scope' => CheckForAnyScope::class, ]; }
- Proteger las rutas en el método __construct del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function __construct(){ $this->middleware('auth:api')->except(['index', 'show']); $this->middleware('scopes:read-post')->only(['index', 'show']); $this->middleware('scopes:create-post')->only(['store']); $this->middleware('scopes:update-post')->only(['update']); $this->middleware('scopes:delete-post')->only(['destroy']); }
- Commit Video 56:
- $ git add .
- $ git commit -m "Video 56: Proteger rutas por scopes"
- $ git push -u origin main
Viedo 57. Asignar scopes a token I
- Abrir el proyecto api.codersfree.
- Modificar la vista api.codersfree\resources\views\tokens\index.blade.php:
<x-app-layout> ≡ <div id="app"> <x-container class="py-8"> {{-- Crear Access Token --}} <x-form-section class="mb-12"> ≡ <div class="grid grid-cols-6 gap-6"> <div class="col-span-6 sm:cok-span-4"> <div v-if="form.errors.length > 0" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded"> <strong class="font-bold">Whoops!</strong> <span>¡Algo salio mal!</span> <ul> <li v-for="error in form.errors"> @{{ error }} </li> </ul> </div> <div> <x-label> Nombre </x-label> <x-input v-model="form.name" type="text" class="w-full mt-1"/> </div> <div v-if="scopes.length > 0"> <x-label>Scopes</x-label> <div v-for="scope in scopes"> <input type="checkbox" name="scopes" :value="scope.id" v-model="form.scopes">@{{scope.id}} </div> </div> </div> </div> ≡ </x-form-section> {{-- @{{form.scopes}} --}} {{-- Mostrar Access Token --}} <x-form-section v-if="tokens.length > 0"> ≡ </x-form-section> </x-container> {{-- Modal show --}} <x-dialog-modal modal="showToken.open"> ≡ </x-dialog-modal> </div> @push('js') <script> new Vue({ el: "#app", data: { tokens: [], scopes: [], form: { name: '', scopes: [], errors: [], disabled: false, }, ≡ }, mounted(){ this.getTokens(); this.getScopes(); }, methods: { getScopes(){ axios.get('/oauth/scopes') .then(response => { this.scopes = response.data; }); }, getTokens(){ ≡ }, show(token){ ≡ }, store(){ this.form.disabled = true; axios.post('/oauth/personal-access-tokens', this.form) .then(response => { this.form.name = ''; this.form.errors = []; this.form.scopes = []; this.form.disabled = false; this.getTokens(); }).catch(error => { this.form.errors = _.flatten(_.toArray(error.response.data.errors)); this.form.disabled = false; }) }, revoke(token){ ≡ }, }, }); </script> @endpush </x-app-layout>
- Commit Video 57:
- $ git add .
- $ git commit -m "Video 57: Asignar scopes a token I"
- $ git push -u origin main
Viedo 58. Asignar scopes a token II
- Abrir el proyecto cliente codersfree:
- Modificar el método setAccessToken del trait codersfree\app\Traits\token.php:
public function setAccessToken($user, $service){ $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'password', 'client_id' => config('services.codersfree.client_id'), 'client_secret' => config('services.codersfree.client_secret'), 'username' => request('email'), 'password' => request('password'), /* 'scope' => 'create-post read-post update-post delete-post' */ /* Como en la línea comentada anteriormente incluimos todos los alcances del scope */ /* la línea siguiente es equivalente a la anterior comentada */ 'scope' => '*' ]); $access_token = $response->json(); $user->accessToken()->create([ 'service_id' => $service['data']['id'], 'access_token' => $access_token['access_token'], 'refresh_token' => $access_token['refresh_token'], 'expires_at' => now()->addSecond($access_token['expires_in']) ]); }
- Modificar el métod resolveAuthorization del controlador codersfree\app\Http\Controllers\Controller.php:
public function resolveAuthorization(){ if(auth()->user()->accessToken->expires_at <= now()){ $response = Http::withHeaders([ 'Accept' => 'application/json' ])->post('http://api.codersfree.test/oauth/token', [ 'grant_type' => 'refresh_token', 'refresh_token' => auth()->user()->accessToken->refresh_token, 'client_id' => config('services.codersfree.client_id'), 'client_secret' => config('services.codersfree.client_secret'), 'scope' => 'create-post read-post update-post delete-post' ]); $access_token = $response->json(); auth()->user()->accessToken->update([ 'access_token' => $access_token['access_token'], 'refresh_token' => $access_token['refresh_token'], 'expires_at' => now()->addSecond($access_token['expires_in']) ]); } }
- Abrir el proyecto cliente2.
- Modificar le método redirect del controlador cliente2\app\Http\Controllers\OauthController.php:
public function redirect(Request $request){ $request->session()->put('state', $state = Str::random(40)); $query = http_build_query([ 'client_id' => config('services.codersfree.client_id'), 'redirect_uri' => route('callback'), 'response_type' => 'code', 'scope' => 'create-post read-post update-post delete-post', 'state' => $state, ]); return redirect('http://api.codersfree.test/oauth/authorize?'.$query); }
- Commit Video 58:
- $ git add .
- $ git commit -m "Video 58: Asignar scopes a token II"
- $ git push -u origin main
Sección 13: Roles y permisos
Viedo 59. Instalar Laravel Permission
- URL Documentación Laravel Permission: https://spatie.be/docs/laravel-permission/v4/introduction
- Abrir el proyecto api.codersfree.
- Instalar Laravel Permission:
- $ composer require spatie/laravel-permission
- Publicar la configuración y las migraciones de Laravel Permission:
- $ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
- Limpiar la configuración del cache:
- $ php artisan optimize:clear ó
- $ php artisan config:clear
- Ejecutar las migraciones:
- $ php artisan migrate
- Incorporar el trait HasRoles al modelo api.codersfree\app\Models\User.php:
<?php ≡ use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable, ApiTrait, HasRoles; ≡ }
- Commit Video 59:
- $ git add .
- $ git commit -m "Video 59: Instalar Laravel Permission"
- $ git push -u origin main
Viedo 60. Asignar roles y permisos
- Abrir el proyecto api.codersfree.
- Crear seeder RoleSeeder:
- $ php artisan make:seeder RoleSeeder
- Implementar el método run del seeder api.codersfree\database\seeders\RoleSeeder.php:Importar los modelos Role y Permission:
public function run() { $admin = Role::create(['name' => 'admin']); $create_post = Permission::create(['name' => 'create posts']); $edit_post = Permission::create(['name' => 'edit posts']); $delete_post = Permission::create(['name' => 'delete posts']); $admin->syncPermissions([ $create_post, $edit_post, $delete_post ]); }
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Permission;
- Asignar rol admin al usuario principal en el método run del seeder api.codersfree\database\seeders\UserSeeder.php:
public function run() { $user = User::create([ 'name' => 'Pedro Bazó', 'email' => 'bazo.pedro@gmail.com', 'password' => bcrypt('12345678') ]); $user->assignRole('admin'); User::factory(99)->create(); }
- Incluir el seeder RoleSeeder en el mètodo run del seeder api.codersfree\database\seeders\DatabaseSeeder.php:
public function run() { Storage::deleteDirectory('posts'); Storage::makeDirectory('posts'); $this->call(RoleSeeder::class); $this->call(UserSeeder::class); Category::factory(4)->create(); Tag::factory(8)->create(); $this->call(PostSeeder::class); }
- Ejecutar nuevamente las migraciones junto a los seeders:
- $ php artisan migrate:fresh --seed
- Nota: al generar nuevamente las migraciones, el cliente tipo password y el cliente tipo personal ya no existen, por tal motivo se tendrán que crear nuevamente.
- Generar nuevamente el cliente tipo password:
- $ php artisan passport:client --password
- What should we name the password grant client? [Laravel Password Grant Client]: (ENTER)
- Which user provider should this client use to retrieve users? [users]: [0] users: (ENTER)
- Credenciales:
Password grant client created successfully. Client ID: 9477eda1-dc56-4076-a5ec-104ac46e8a65 Client secret: 5qLzeZgFi6pDr8GMAUvriYVVaFTnoQe7Gz0rvXdQ
- Pasar las credenciales al proyecto codersfree:
- Abrir el proyecto codersfree.
- Reemplazar las credenciales del archivo de variables de entorno codersfree\.env:
CODERSFREE_CLIENT_ID="9477eda1-dc56-4076-a5ec-104ac46e8a65" CODERSFREE_CLIENT_SECRET="5qLzeZgFi6pDr8GMAUvriYVVaFTnoQe7Gz0rvXdQ"
- Poner en cache los datos de configuración:
- $ php artisan config:cache
- Reestablecer las migraciones:
- $ php artisan migrate:fresh
- Generar nuevamente el cliente tipo personal:
- $ php artisan passport:client --personal
- What should we name the personal access client? [Laravel Personal Access Client]: (ENTER)
- Credenciales:
Personal access client created successfully. Client ID: 9477f0e3-bbc5-417b-896b-485e2eba2196 Client secret: MkZvYFoijbDZRma7HcpDeO0ukOARnYgQTQhSjddo
- Nota: No es estrictamente necesario almacenar estas credenciales, ya que el proyecto las tomará por defecto.
- Commit Video 60:
- $ git add .
- $ git commit -m "Video 60: Asignar roles y permisos"
- $ git push -u origin main
Viedo 61. Proteger rutas con roles y policies
- Abrir el proyecto api.codersfree.
- Con la finalidad de proteger las rutas modificar el métdo __construct del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
public function __construct(){ $this->middleware('auth:api')->except(['index', 'show']); $this->middleware('scopes:read-post')->only(['index', 'show']); $this->middleware(['scopes:create-post', 'can:create posts'])->only(['store']); $this->middleware(['scopes:update-post', 'can:edit posts'])->only(['update']); $this->middleware(['scopes:delete-post', 'can:delete posts'])->only(['destroy']); }
- Crear el policy PostPolicy para el modelo Post:
- $ php artisan make:policy PostPolicy --model=Post
- Redefinir el policy api.codersfree\app\Policies\PostPolicy.php:
<?php namespace App\Policies; use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; class PostPolicy { use HandlesAuthorization; public function author(User $user, Post $post) { if ($post->user_id == $user->id) { return true; }else{ return false; } } }
- Proteger los métodos update y destroy del controlador api.codersfree\app\Http\Controllers\Api\PostController.php:
≡ class PostController extends Controller { ≡ public function update(Request $request, Post $post) { // Para proteger esta ruta se invoca al método authorize y se le // pasa como parámetros el nombre del policy y la instancia del post $this->authorize('authos', $post); $request->validate([ 'name' => 'required|max:255', 'slug' => 'required|max:255|unique:posts,slug,' . $post->id, 'extract' => 'required', 'body' => 'required', 'category_id' => 'required|exists:categories,id', 'user_id' => 'required|exists:users,id' ]); $post->update($request->all()); return PostResource::make($post); } ≡ public function destroy(Post $post) { // Para proteger esta ruta se invoca al método authorize y se le // pasa como parámetros el nombre del policy y la instancia del post $this->authorize('authos', $post); $post->delete(); return PostResource::make($post); } }
- Commit Video 61:
- $ git add .
- $ git commit -m "Video 61: Proteger rutas con roles y policies"
- $ git push -u origin main
Viedo 62. Despedida del curso
- Contenido: comentarios de lo aprendido durante el curso.
- Commit Video 62:
- $ git add .
- $ git commit -m "Video 62: Despedida del curso"
- $ git push -u origin main
Repositorios de interes:
Para limpiar configuración y reestablecer el cache:
- $ php artisan config:clear
- $ php artisan config:cache
En caso de no permitir compilar algo:
- $ php artisan clear-compiled
- $ composer dumpautoload
Peticiones http que puede responder el proyecto api.restful:
Usuarios:
Registrar un usuario:
- Método: POST
- URL: http://api.codersfree.test/v1/register
- Body:
- Form:
Field name: name | Value: Pedro Bazó Field name: email | Value: bazo.pedro@gmail.com Field name: password | Value: 12345678 Field name: password_confirmation | Value: 12345678
- Form:
- Headers:
Header: Accept | Value: application/json
Login a un usuario:
- Método: POST
- URL: http://api.codersfree.test/v1/login
- Body:
- Form:
Field name: email | Value: bazo.pedro@gmail.com Field name: password | Value: 12345678
- Form:
- Headers:
Header: Accept | Value: application/json
Permisos de accesos:
Obtener token para un usuario:
- Método: POST
- URL: http://api.codersfree.test/oauth/token
- Body:
- Form:
Field name: grant_type | Value: password Field name: client_id | Value: {Client ID del cliente tipo password} Field name: client_secret | Value: {Client secret del cliente tipo password} Field name: username | Value: {email del usuario a autorizar} Field name: password | Value: {clave del usuario}
- Form:
- Body:
- Headers:
Header: Accept | Value: application/json
Obtener autorización por cliente tipo password:
- Headers:
Header: Accept | Value: application/json Header: Authorization | Value: Bearer + (un espacio) + (access_token de la petición anterior sin las comillas dobles)
Categorías:
Obtener las categorías:
- Método: GET
- URL: http://api.codersfree.test/v1/categories
- Headers:
Header: Accept | Value: application/json
Crear una categoría:
- Método: POST
- URL: http://api.codersfree.test/v1/categories
- Body:
- Form:
Field name: name | Value: Categoría de prueba Field name: slug | Value: categoria-de-prueba
- Form:
- Headers:
Header: Accept | Value: application/json
Obtener una categoría:
- Método: GET
- URL: http://api.codersfree.test/v1/categories/{id}
- Headers:
Header: Accept | Value: application/json
Actualizar una categoría:
- Método: PUT
- URL: http://api.codersfree.test/v1/categories/{id}
- Body:
- Form-encode:
Field name: name | Value: Categoría de prueba actualizada Field name: slug | Value: categoria-de-prueba-actualizada
- Form-encode:
- Headers:
Header: Accept | Value: application/json
Eliminar una categoría:
- Método: DELETE
- URL: http://api.codersfree.test/v1/categories/{id}
- Headers:
Header: Accept | Value: application/json
Obtener las categorías y su relación con los posts:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?included=posts
- Headers:
Header: Accept | Value: application/json
Obtener las categorías y su relación con los posts y el autor del post:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?included=posts.user
- Headers:
Header: Accept | Value: application/json
Obtener una categoría y su relación con los posts:
- Método: GET
- URL: http://api.codersfree.test/v1/categories/{id}?included=posts
- Headers:
Header: Accept | Value: application/json
Obtener una categoría y su relación con los posts y el autor del post:
- Método: GET
- URL: http://api.codersfree.test/v1/categories/1?included=posts.user
- Headers:
Header: Accept | Value: application/json
Obtener las categorías filtradas:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?filter[{Campo1}]={Valor1}&filter[{Campo2}]={Valor2}
- Headers:
Header: Accept | Value: application/json
Obtener las categorías ordenadas:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?sort={Campo1,Campo2}
- Headers:
Header: Accept | Value: application/json
- Nota: Las categorías se ordenaran en orden ascendente, si se desea que se ordenen de manera descendente el campo debe ser precedido por el signo menos (-).
Obtener las categorías paginadas:
- Método: GET
- URL: http://api.codersfree.test/v1/categories?perPage{RegistrosPorPágina}&page={Página}
- Headers:
Header: Accept | Value: application/json
Posts
Obtener los posts:
- Método: GET
- URL: http://api.codersfree.test/v1/posts
- Headers:
Header: Accept | Value: application/json
Nota: para relacionar, ordenar, filtrar y paginar es análogo a como se hace para las categorías.
Registrar un post:
- Método: POST
- URL: http://api.codersfree.test/v1/posts
- Body:
- Form:
Field name: name | Value: Título de prueba Field name: slug | Value: titulo-de-prueba Field name: extract | Value: Cualquier cosa Field name: body | Value: Cualquier cosa Field name: category_id | Value: 1 Field name: user_id | Value: 1
- Form:
- Body:
- Headers:
Header: Accept | Value: application/json
Comentarios
Publicar un comentario