web push notification

para el client que recibira las notificaciones

https://www.blog.plint-sites.nl/progressive-web-app-using-vue-cli-3/

hay que crear un archivo en la raiz del proyecto llamado .env

con las sgts variables:

VUE_APP_VAPID_PUBLIC_KEY=la que se haya generado en el backend
VUE_APP_API_PATH=http://127.0.0.1:8000/api

recordar que para que funcione se debe levantar la version en produccion, es decir

1) para generar la carpeta dist
npm run build
2) para levantar el servidor (previamente instalar globalmente el comando serve:

npm install -g serve )
serve -s dist
3) en el navegador ingresar http://127.0.0.1:5000/

aunque se puede abrir la pagina a traves de http://localhost:5000 cuando se envien las push notifications no las lee.

en el  backend:
https://www.blog.plint-sites.nl/how-to-add-push-notifications-to-a-progressive-web-app/?fbclid=IwAR0Ts7ydULV0DairqZaSxoeBA5cl-husEXtx2HwTfnj3B-WRWPUxJywY5iw

instalar Cors
https://github.com/barryvdh/laravel-cors

y crear la tabla de notifications

php artisan make:migration create_notifications_table

  $table->uuid('id')->primary();
            $table->string('type');
            $table->morphs('notifiable');
            $table->text('data');
            $table->timestamp('read_at')->nullable();
            $table->timestamps();

y el controller

php artisan make:controller NotificationController

git diff entre branches solo nombres de archivos con diferencias

git diff otroBranch --name-only

laravel5.8 authentication jwt


fuente original:

https://blog.ezteven.com/tech/2019/05/30/utiliza-jwt-con-laravel-para-apis.html
creamos el proyecto:

composer create-project --prefer-dist laravel/laravel webpage "5.8.*"


en la carpeta del proyecto creado:

composer require tymon/jwt-auth:1.0.*

actualizar las librerias:

composer update

[para linux] cambiar el owner de la carpeta del proyecto:

desde la carpeta superior a la del proyecto...

sudo chown -R ibazan:ibazan webpage

generar la key para jwt:

php artisan jwt:secret


publicar el servicio:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

el modelo de user:

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Foundation\Auth\User  as Authenticatable;

class  User  extends  Authenticatable  implements  JWTSubject {
use  Notifiable;

protected  $fillable = [
'name', 'surname', 'email', 'password',
];

protected  $hidden = [
'password', 'remember_token',
];

protected  $casts = [
'email_verified_at' => 'datetime',
];

public  function  getJWTIdentifier() {
return  $this->getKey();
}

public  function  getJWTCustomClaims() {
return [];
}
}

editamos config/auth.php

'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],

creamos el AuthController:

php artisan make:controller AuthController

editamos el AuthController.php:

use App\Http\Requests\RegisterAuthRequest;
use App\User;
use Illuminate\Http\Request;
use  JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
class  AuthController extends  Controller {
public  $loginAfterSignUp = true;

public  function  register(Request  $request) {
$user = new  User();
$user->name = $request->name;
$user->surname = $request->surname;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();

if ($this->loginAfterSignUp) {
return  $this->login($request);
}

return  response()->json([
'status' => 'ok',
'data' => $user
], 200);
}

public  function  login(Request  $request) {
$input = $request->only('email', 'password');
$jwt_token = null;
if (!$jwt_token = JWTAuth::attempt($input)) {
return  response()->json([
'status' => 'invalid_credentials',
'message' => 'Correo o contraseña no válidos.',
], 401);
}

return  response()->json([
'status' => 'ok',
'token' => $jwt_token,
]);
}

public  function  logout(Request  $request) {
$this->validate($request, [
'token' => 'required'
]);

try {
JWTAuth::invalidate($request->token);
return  response()->json([
'status' => 'ok',
'message' => 'Cierre de sesión exitoso.'
]);
} catch (JWTException  $exception) {
return  response()->json([
'status' => 'unknown_error',
'message' => 'Al usuario no se le pudo cerrar la sesión.'
], 500);
}
}

public  function  getAuthUser(Request  $request) {
$this->validate($request, [
'token' => 'required'
]);

$user = JWTAuth::authenticate($request->token);
return  response()->json(['user' => $user]);
}
}

creamos las rutas a las apis:

use Illuminate\Http\Request;

// estas rutas se pueden acceder sin proveer de un token válido.
Route::post('/login', 'AuthController@login');
Route::post('/register', 'AuthController@register');
// estas rutas requiren de un token válido para poder accederse.
Route::group(['middleware' => 'jwt.auth'], function () {
    Route::post('/logout', 'AuthController@logout');
});

editar en Http/Kernel.php:

protected $routeMiddleware = [
  ...
 'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
];

Adicionalmente podemos añadir al comienzo del public\index.php el siguiente código para evitar error de CORS durante nuestras pruebas:

// permite peticiones desde cualquier origen
header('Access-Control-Allow-Origin: *');
// permite peticiones con métodos GET, PUT, POST, DELETE y OPTIONS
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
// permite los headers Content-Type y Authorization
header('Access-Control-Allow-Headers: Content-Type, Authorization');

..y eso es todo!

ya podemos probar el registro:


post: http://127.0.0.1:8000/api/register

{
"name":"juan",
"surname":"perez",
"email":"jperez@mail.com",
"password":"123456",
}

lo que devolvera

{
"status":"ok",
"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjE6ODAwMFwvYXBpXC9yZWdpc3RlciIsImlhdCI6MTU2ODA1MDI2MywiZXhwIjoxNTY4MDUzODYzLCJuYmYiOjE1NjgwNTAyNjMsImp0aSI6ImtnOElmbmxJQ2w3TXJPUWoiLCJzdWIiOjIsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ.6rMzjaqKneDgMtr0DVlCQGoFZVUhi7ZSOjH3EoF0HWU"
}

para desloguearnos debemos enviar el token, por ejemplo como parametro
post: http://127.0.0.1:8000/api/logout
{
"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjE6ODAwMFwvYXBpXC9yZWdpc3RlciIsImlhdCI6MTU2ODA1MDI2MywiZXhwIjoxNTY4MDUzODYzLCJuYmYiOjE1NjgwNTAyNjMsImp0aSI6ImtnOElmbmxJQ2w3TXJPUWoiLCJzdWIiOjIsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ.6rMzjaqKneDgMtr0DVlCQGoFZVUhi7ZSOjH3EoF0HWU"
}

lo que devolvera

{
"status": "ok",
"message": "Cierre de sesión exitoso."
}




actualiza npm en linux

1
2
3
sudo npm cache clean -f
sudo npm install -g n
sudo n stable


cerrar la terminal y abrirla nuevamente
para comprobar la version instalada

npm -v

laravel 5.8 crud con vue spa

fuente original:
https://appdividend.com/2018/11/17/vue-laravel-crud-example-tutorial-from-scratch

parte 1
creamos la aplicacion

laravel new vue_laravel_crud

(ya te crea el .env con la key)

en linux dar permisos a la carpeta del proyecto

chmod -R 777 vue_laravel_crud

vamos a la carpeta creada vue_laravel_crud
e instalamos las dependencias del frontend.

npm install 

(en linux usar sudo npm install)

se puede ahora ejecutar npm run dev para compilar los assets y guardarlos
o ejecutar npm run watch para que los compile mientras se cree codigo nuevo o se modifique el existente

Parte 2

instalar vue-router y vue-axios

router para manejar el direccionamiento
axios para manejar las peticiones al servidor

npm install vue-router vue-axios --save


y configuramos estas librerias en

rosurces/js/app.js



import VueRouter from 'vue-router';
Vue.use(VueRouter);

import VueAxios from 'vue-axios';
import axios from 'axios';
Vue.use(VueAxios, axios);

const router = new VueRouter({ mode: 'history'});
const app = new Vue(Vue.util.extend({ router })).$mount('#app');


y ahora en resources/views/ creamos post.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">
        <link href="{{ mix('css/app.css') }}" type="text/css" rel="stylesheet" />
        <meta name="csrf-token" value="{{ csrf_token() }}" />
    </head>
    <body>
        <div id="app">
         
        </div>
        <script src="{{ mix('js/app.js') }}" type="text/javascript"></script>
    </body>
</html>


ahora editamos routes/web.php

<?php

Route::get('/{any}', function () {
  return view('post');
})->where('any', '.*');

para poder capturar cualquier ruta que se escriba en el browser
en el sector de id="app" vamos a cargar el component App.vue que contendra nuestro
router-view
si existe el router lo renderizara sino no. y quedara vacio.

resources/js/App.vue

<template>
<div class="container">
<transition name="fade">
<router-view></router-view>
</transition>
</div>
</template>

<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-active {
opacity: 0
}
</style>

<script>

export default{
mounted() {
console.log('App.vue mounted.')
}
}
</script>


editaremos app.js para crear las routes de los componentes importados y asignarselo a nuestro
objeto Vue de la aplicacion

import HomeComponent from './components/HomeComponent.vue';
import CreateComponent from './components/CreateComponent.vue';
import IndexComponent from './components/IndexComponent.vue';
import EditComponent from './components/EditComponent.vue';

const routes = [
{
name: 'home',
path: '/home',
component: HomeComponent
},
{
name: 'create',
path: '/create',
component: CreateComponent
},
{
name: 'posts',
path: '/posts',
component: IndexComponent
},
{
name: 'edit',
path: '/edit/:id',
component: EditComponent
}
];

import App from './App.vue';
const router = new VueRouter({ mode: 'history', routes: routes});
const app = new Vue(
Vue.util.extend({ router }, App)
).$mount('#app');

los componentes
hay que crearlos en resources/js/components/

por ejemplo el CreateComponent.vue seria

<template>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card card-default">
<div class="card-header">Create Component</div>

<div class="card-body">
I'm the Create Component component.
</div>
</div>
</div>
</div>
</template>

<script>
export default {
mounted() {
console.log('CreateComponent mounted.')
}
}
</script>

al App.vue
le vamos a agregar una cabecera para navegar

<template>
<div class="container">
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<ul class="navbar-nav">
<li class="nav-item">
<router-link to="/home" class="nav-link">Home</router-link>
</li>
<li class="nav-item">
<router-link to="/create" class="nav-link">Create Post</router-link>
</li>
<li class="nav-item">
<router-link to="/posts" class="nav-link">Posts</router-link>
</li>
</ul>
</nav><br />
<transition name="fade">
<router-view></router-view>
</transition>
</div>
</template>

Parte 3: Crear el Backend

Vamos a crear el modelo Post, el controlador y una collection de Post como resorce.

php artisan make:model Post -mc

-mc : crea el archivo de migracion y el controlador de un saque

para crear la collection PostCollection

php artisan make:resource PostCollection

editar en el archivo de migracion:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title');
$table->text('body');
$table->timestamps();
});
}

en el modelo Post.php, indicar los campos que se pueden llenar masivamente:

protected $fillable = ['title', 'body'];

implementamos los metodos del controlador PostController.php

use Illuminate\Http\Request;
use App\Http\Resources\PostCollection;
use App\Post;

class PostController extends Controller
{
public function store(Request $request)
{
$post = new Post([
'title' => $request->get('title'),
'body' => $request->get('body')
]);

$post->save();

return response()->json('successfully added');
}

public function index()
{
return new PostCollection(Post::all());
}

public function edit($id)
{
$post = Post::find($id);
return response()->json($post);
}

public function update($id, Request $request)
{
$post = Post::find($id);

$post->update($request->all());

return response()->json('successfully updated');
}

public function delete($id)
{
$post = Post::find($id);

$post->delete();

return response()->json('successfully deleted');
}
}

Parte 4: Definir las routes en la api

en routes/api.php:

use Illuminate\Http\Request;
Route::post('/post/create', 'PostController@store');
Route::get('/post/edit/{id}', 'PostController@edit');
Route::post('/post/update/{id}', 'PostController@update');
Route::delete('/post/delete/{id}', 'PostController@delete');
Route::get('/posts', 'PostController@index');


Parte 5: Implementar los componentes de Creacion, Listado y actualizacion

notar el uso de axios para los request a nuestra api

CreateComponent.vue
<template>
<div>
<h1>Create A Post</h1>
<form @submit.prevent="addPost">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Post Title:</label>
<input type="text" class="form-control" v-model="post.title">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Post Body:</label>
<textarea class="form-control" v-model="post.body" rows="5"></textarea>
</div>
</div>
</div><br />
<div class="form-group">
<button class="btn btn-primary">Create</button>
</div>
</form>
</div>
</template>

<script>
export default {
mounted() {
console.log('CreateComponent mounted.')
},
data(){
return {
post:{}
}
},
methods: {
addPost(){
console.log(this.post);
let server = 'http://127.0.0.1:8001';
let uri = server + '/api/post/create';
this.axios.post(uri, this.post).then((response) => {
this.$router.push({name: 'posts'});
});
}
}
}
</script>

we have used the push() method to change the route programmatically.

IndexComponent.vue
<template>
<div>
<h1>Posts</h1>
<div class="row">
<div class="col-md-10"></div>
<div class="col-md-2">
<router-link :to="{ name: 'create' }" class="btn btn-primary">Create Post</router-link>
</div>
</div><br />

<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Item Name</th>
<th>Item Body</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="post in posts" :key="post.id">
<td>{{ post.id }}</td>
<td>{{ post.title }}</td>
<td>{{ post.body }}</td>
<td><router-link :to="{name: 'edit', params: { id: post.id }}" class="btn btn-primary">Edit</router-link></td>
<td><button class="btn btn-danger" @click.prevent="deletePost(post.id)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
export default {
mounted() {
console.log('IndexComponent mounted.')
},
data() {
return {
posts: []
}
},
created() {
let server = 'http://127.0.0.1:8001';
let uri = server + '/api/posts';
this.axios.get(uri).then(response => {
this.posts = response.data.data;
});
},
methods: {
deletePost(id)
{
let server = 'http://127.0.0.1:8001';
let uri = `${server}/api/post/delete/${id}`;
this.axios.delete(uri).then(response => {
let i = this.posts.map(item => item.id).indexOf(id); // find index of your object
this.posts.splice(i, 1)
});
}
}
}
</script>
en el listado se colocará acciones por cada item: link de editar y boton de eliminar
si se elimina un item el listado se modifica sin que se recargue la pagina

EditComponent.vue

<template>
<div>
<h1>Edit Post</h1>
<form @submit.prevent="updatePost">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Post Title:</label>
<input type="text" class="form-control" v-model="post.title">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Post Body:</label>
<textarea class="form-control" v-model="post.body" rows="5"></textarea>
</div>
</div>
</div><br />
<div class="form-group">
<button class="btn btn-primary">Update</button>
</div>
</form>
</div>
</template>

<script>
export default {
mounted() {
console.log('EditComponent mounted.')
},
data() {
return {
post: {}
}
},
created() {
let server = 'http://127.0.0.1:8001';
let uri = `${server}/api/post/edit/${this.$route.params.id}`;
this.axios.get(uri).then((response) => {
this.post = response.data;
});
},
methods: {
updatePost() {
let server = 'http://127.0.0.1:8001';
let uri = `${server}/api/post/update/${this.$route.params.id}`;
this.axios.post(uri, this.post).then((response) => {
this.$router.push({name: 'posts'});
});
}
}
}
</script>

por un tema de organizacion de codigo, las routes se pueden definir en un arhivo e importarlo luego
en app.js.

entonces creamos en resorces/js/routes.js

import HomeComponent from './components/HomeComponent.vue';
import CreateComponent from './components/CreateComponent.vue';
import IndexComponent from './components/IndexComponent.vue';
import EditComponent from './components/EditComponent.vue';
const routes = [
{
name: 'home',
path: '/home',
component: HomeComponent
},
{
name: 'create',
path: '/create',
component: CreateComponent
},
{
name: 'posts',
path: '/posts',
component: IndexComponent
},
{
name: 'edit',
path: '/edit/:id',
component: EditComponent
}
];

export default routes;

y en app.js

import routes_file from './routes';

const router = new VueRouter({ mode: 'history', routes: routes_file});

extraer un archivo rar en linux

1 instalar winrar

sudo apt-get install unrar

2 extraer el archivo

unrar e mis_archivos.rar

asi se extraeran los archivos comprimidos en la ubicacion actual. Si se quiere enviar los archivos extraidos a una carpeta

unrar e mis_archivos.rar mi_carpeta/

linux ubuntu mint actualizar chrome

 desde una terminal: $ sudo apt update $ sudo apt install google-chrome-stable