Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/petrix12/laravel-vue2024

Proyecto completo de ejemplo de una app en laravel + vue
https://github.com/petrix12/laravel-vue2024

Last synced: 8 days ago
JSON representation

Proyecto completo de ejemplo de una app en laravel + vue

Awesome Lists containing this project

README

        

# Taller de Laravel + Vue

## Creación del backend
1. Crear proyecto backend:
```bash
laravel new speaksmarter
```
:::tip Nota
Seleccionar las opciones por defecto.
:::
2. Crear base de datos **speaksmarter**.
3. Configurar el archivo de variables de entorno **.env**:
```env title=".env"
APP_NAME=Speaksmarter
# ...
APP_URL=http://speaksmarter.test
# ...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=speaksmarter
DB_USERNAME=root
DB_PASSWORD=
# ...
```
4. Instalar **Jetstream** e **Inertia**:
```bash
composer require laravel/jetstream
php artisan jetstream:install inertia
npm i @inertiajs/inertia
```
5. Instalar sistema de roles y permisos con **Laravel-Spatie**:
```bash
composer require spatie/laravel-permission
```
:::tip Documentación
SPATIE: https://spatie.be/docs/laravel-permission/v6/installation-laravel
:::
6. Publicar las migraciones de **Laravel-Spatie** y el archivo de configuración **permission**:
```bash
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
```
7. Agregar el trade **HasRoles** al modelo **User**:
```php title="app\Models\User.php"
// ...
use Spatie\Permission\Traits\HasRoles;
// ...
class User extends Authenticatable
{
// ...
use HasRoles;
// ...
}
```
8. Ejecutar las migraciones:
```bash
php artisan migrate
```

## Entidades principales
+ Módulo Usuarios, Roles y Permisos:
+ USERS
+ ROLES
+ PERMMISIONS
+ Módulo Lecciones:
+ LESSONS
+ CATEGORY
+ LEVELS

## Modelar la estructura básica para el módulo de lecciones
1. Crear los modelos **Lesson**, **Category** y **Level**:
```bash
php artisan make:model Level -m
php artisan make:model Lesson -m
php artisan make:model Category -m
```
2. Crear migraciones para tablas pivotes:
```bash
php artisan make:migration create_category_lesson_table
```
3. Establecer los campos de la tabla **lessons**:
```php title="database\migrations\2024_06_26_051845_create_lessons_table.php"
// ...
public function up(): void
{
Schema::create('lessons', function (Blueprint $table) {
$table->id();
$table->string('name', 150);
$table->text('description')->max(400);
$table->string('image_uri', 255)->nullable();
$table->string('content_uri', 255);
$table->string('pdf_uri', 255);
$table->boolean('is_free')->default(false);
$table->unsignedBigInteger('level_id');
$table->foreign('level_id')->references('id')->on('levels')->onUpdate('cascade')->onDelete('cascade');
$table->timestamps();
});
}
// ...
```
4. Establecer los campos de la tabla **categories**:
```php title="database\migrations\2024_06_26_052002_create_categories_table.php"
// ...
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name', 100);
$table->timestamps();
});
}
// ...
```
5. Establecer los campos de la tabla **levels**:
```php title="database\migrations\2024_06_26_050146_create_levels_table.php"
// ...
public function up(): void
{
Schema::create('levels', function (Blueprint $table) {
$table->id();
$table->string('name', 2);
$table->timestamps();
});
}
// ...
```
6. Establecer los campos de la tabla auxiliar **category_lesson**:
```php title="database\migrations\2024_06_26_052330_create_category_lesson_table.php"
// ...
public function up(): void
{
Schema::create('category_lesson', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('category_id');
$table->unsignedInteger('lesson_id');
$table->timestamps();
});
}
// ...
```
7. Configurar el modelo **Lesson**:
```php title="app\Models\Lesson.php"
// ...
class Lesson extends Model
{
// ...
protected $guarded = [];

public function categories(){
return $this->belongsToMany(Category::class);
}

public function level() {
return $this->belongsTo(Level::class);
}
}
```
8. Configurar el modelo **Category**:
```php title="app\Models\Category.php"
// ...
class Level extends Model
{
// ...
protected $guarded = [];

public function lessons() {
return $this->hasMany(Lesson::class);
}
}
```
9. Configurar el modelo **Level**:
```php title="app\Models\Level.php"
// ...
class Category extends Model
{
// ...
protected $guarded = [];

public function lessons() {
return $this->belongsToMany(Lesson::class);
}
}
```
10. Crear seeder para poblar la tabla **levels**:
```bash
php artisan make:seeder LevelSeeder
```
11. Programar seeder **LevelSeeder**:
```php title="database\seeders\LevelSeeder.php"
// ...
use App\Models\Level;
// ...
class LevelSeeder extends Seeder
{
public function run(): void
{
Level::create(['name' => 'A1']);
Level::create(['name' => 'A2']);
Level::create(['name' => 'B1']);
Level::create(['name' => 'B2']);
Level::create(['name' => 'C1']);
Level::create(['name' => 'C2']);
}
}
```
12. Ejecutar migraciones:
```bash
php artisan migrate
```

## Modelar la estructura básica para el módulo de usuarios, roles y permisos
1. Crear seeders **RoleSeeder** y **UserSeeder**:
```bash
php artisan make:seeder RoleSeeder
php artisan make:seeder UserSeeder
```
2. Programar seeder **RoleSeeder**:
```php title="database\seeders\RoleSeeder.php"
// ...
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
// ...
class RoleSeeder extends Seeder
{
// ...
public function run(): void
{
$role_admin = Role::create(['name' => 'admin']);
$role_editor = Role::create(['name' => 'editor']);

$permission_create_role = Permission::create(['name' => 'create roles']);
$permission_read_role = Permission::create(['name' => 'read roles']);
$permission_update_role = Permission::create(['name' => 'update roles']);
$permission_delete_role = Permission::create(['name' => 'delete roles']);

$permission_create_lesson = Permission::create(['name' => 'create lessons']);
$permission_read_lesson = Permission::create(['name' => 'read lessons']);
$permission_update_lesson = Permission::create(['name' => 'update lessons']);
$permission_delete_lesson = Permission::create(['name' => 'delete lessons']);

$permission_create_category = Permission::create(['name' => 'create categories']);
$permission_read_category = Permission::create(['name' => 'read categories']);
$permission_update_category = Permission::create(['name' => 'update categories']);
$permission_delete_category = Permission::create(['name' => 'delete categories']);

$permissions_admin = [
$permission_create_role,
$permission_read_role,
$permission_update_role,
$permission_delete_role,
$permission_create_lesson,
$permission_read_lesson,
$permission_update_lesson,
$permission_delete_lesson,
$permission_create_category,
$permission_read_category,
$permission_update_category,
$permission_delete_category
];
$permissions_editor = [
$permission_create_lesson,
$permission_read_lesson,
$permission_update_lesson,
$permission_delete_lesson,
$permission_create_category,
$permission_read_category,
$permission_update_category,
$permission_delete_category
];

$role_admin->syncPermissions($permissions_admin);
$role_editor->syncPermissions($permissions_editor);
}
}
```
3. Programar seeder **UserSeeder**:
```php title="database\seeders\UserSeeder.php"
// ...
use App\Models\User;
use Illuminate\Support\Facades\Hash;
// ...

class UserSeeder extends Seeder
{
// ...
public function run(): void
{
$admin = User::create([
'name' => 'admin',
'email' => '[email protected]',
'password' => Hash::make('admin'),
])->assignRole('admin');

$editor = User::create([
'name' => 'editor',
'email' => '[email protected]',
'password' => Hash::make('editor'),
])->assignRole('editor');
}
}
```
4. Modificar el seeder principal **DatabaseSeeder** para incluir los seeders **LevelSeeder** (Creado en el modelado de lecciones), **RoleSeeder** y **UserSeeder**:
```php title="database\seeders\DatabaseSeeder.php"
public function run(): void
{
$this->call([
LevelSeeder::class,
RoleSeeder::class,
UserSeeder::class
]);
}
```
5. Poner a disposición de los componentes vue de inertia los roles y permisos modificando el middleware **HandleInertiaRequests** para pasar por props la información indicada a los componentes:
```php title="app\Http\Middleware\HandleInertiaRequests.php"
// ...
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'user.roles' => $request->user() ? $request->user()->roles()->pluck('name') : [],
'user.permissions' => $request->user() ? $request->user()->getPermissionsViaRoles()->pluck('name') : []
]);
}
// ...
```
:::tip Nota
+ Para acceder a los props desde cualquier vista de inertia:
```html
{{ $page.props }}
```
+ Para restringir un bloque de código html según el rol:
```html
Crear rol
```
:::
6. Ejecutar los seeder:
```bash
php artisan db:seed
```
7. Instalar las dependencias npm:
```bash
npm i
npm run dev
```

## Panel de administración
1. Crear controlador **DashboardController**:
```bash
php artisan make:controller DashboardController
```
2. Programar controlador **DashboardController**:
```php title="app\Http\Controllers\DashboardController.php"
// ...
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

class DashboardController extends Controller
{
public function index() {
return Inertia::render('Welcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
}

public function dashboard() {
return Inertia::render('Dashboard');
}
}
```
3. Crear los controladores **CategoryController**, **LessonController** y **RoleController** con todos sus recursos:
```bash
php artisan make:controller CategoryController -r
php artisan make:controller LessonController -r
php artisan make:controller RoleController -r
```
4. Crear custom request **CategoryRequest**:
```bash
php artisan make:request CategoryRequest
```
5. Programar custom request **CategoryRequest**:
```php title="app\Http\Requests\CategoryRequest.php"
// ...
use Illuminate\Validation\Rule;

class CategoryRequest extends FormRequest
{
// ...
public function authorize(): bool
{
return true;
}
// ...
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100', Rule::unique(table: 'categories', column: 'name')->ignore(id: request('category'), idColumn: 'id')]
];
}

public function messages(): array
{
return [
'name.unique' => __('Category already exists'),
];
}
}
```
6. Programar controlador **CategoryController**:
```php title="app\Http\Controllers\CategoryController.php"
// ...
use App\Http\Requests\CategoryRequest;
use App\Models\Category;
use Inertia\Ssr\Response;

class CategoryController extends Controller
{
const NOMBER_OF_ITEMS_PER_PAGE = 25;

public function index()
{
//define('NOMBER_OF_ITEMS_PER_PAGE', 25);
$categories = Category::paginate(self::NOMBER_OF_ITEMS_PER_PAGE);
return inertia('Categories/Index', compact('categories'));
}

public function create()
{
return inertia('Categories/Create');
}

public function store(CategoryRequest $request)
{
Category::create($request->validated());
return redirect()->route('categories.index');
}

public function show(string $id) {}

public function edit(Category $category)
{
return inertia('Categories/Edit', compact('category'));
}

public function update(CategoryRequest $request, Category $category)
{
$category->update($request->validated());
return redirect()->route('categories.index');
}

public function destroy(Category $category)
{
$category->delete();
return redirect()->route('categories.index');
}
}
```
7. Programar controlador **LessonController**:
```php title="app\Http\Controllers\LessonController.php"
```
8. Programar controlador **RoleController**:
```php title="app\Http\Controllers\RoleController.php"
```
9. Establecer las rutas administrativas en el archivo de rutas **web**:
```php title="routes\web.php"
group(function () {
// Rutas autenticadas
Route::get('/dashboard', [DashboardController::class, 'dashboard'])->name('dashboard');
Route::resource('/categories', \App\Http\Controllers\CategoryController::class);
Route::resource('/lessons', \App\Http\Controllers\LessonController::class);
Route::resource('/roles', \App\Http\Controllers\RoleController::class);
});
```
10. Adaptar la plantilla **AppLayout** a nuestra aplicación:
```php title="resources\js\Layouts\AppLayout.vue"











Dashboard




Categories




Lessons




Roles



```
11. Diseñar las vistas para las categorias:
1. Crear vista **Categories/Index**:
```html title="resources\js\Pages\Categories\Index.vue"

export default {
name: 'CategoriesIndex'
}


import AppLayout from '@/Layouts/AppLayout.vue'
import { Link } from '@inertiajs/vue3'
import { Inertia } from '@inertiajs/inertia'

defineProps({
categories: {
type: Object,
required: true
}
})

const deleteCategory = (id) => {
if(confirm('Are you sure?')) {
Inertia.delete(route('categories.destroy', id))
}
}





Categories








Create category












#


Name


Edit


Delete






{{ category.id }}


{{ category.name }}



Edit




Delete













PREV



NEXT








```
2. Crear vista **Categories/Create**:
```html title="resources\js\Pages\Categories\Create.vue"

export default {
name: 'CategoriesCreate'
}


import AppLayout from '@/Layouts/AppLayout.vue'
import { useForm } from '@inertiajs/vue3'
import CategoryForm from '@/Components/Categories/Form.vue'

const form = useForm({
name: ''
})





Create category












```
3. Crear vista **Categories/Edit**:
```html title="resources\js\Pages\Categories\Edit.vue"

export default {
name: 'CategoriesEdit'
}


import AppLayout from '@/Layouts/AppLayout.vue'
import { useForm } from '@inertiajs/vue3'
import CategoryForm from '@/Components/Categories/Form.vue'

const props = defineProps({
category: {
type: Object,
required: true
}
})

const form = useForm({
name: props.category.name
})





Edit category












```
4. Crear componente **Components/Categories/Form.vue**:
```html title="resources\js\Components\Categories\Form.vue"

export default {
name: 'CategoriesForm'
}


import FormSection from '@/Components/FormSection.vue'
import InputError from '@/Components/InputError.vue'
import InputLabel from '@/Components/InputLabel.vue'
import PrimaryButton from '@/Components/PrimaryButton.vue'
import TextInput from '@/Components/TextInput.vue'

defineProps({
form: {
type: Object,
required: true
},
updating: {
type: Boolean,
required: false,
default: false
}
})

defineEmits(['submit'])




{{ updating ? 'Update Category' : 'Create Category' }}


{{ updating ? 'Update your category' : 'Create a new category' }}










{{ updating ? 'Update' : 'Create' }}




```
12. Diseñar las vistas para las lecciones:
1. Crear vista **Lessons/Index**:
```html title="resources\js\Pages\Lessons\Index.vue"

export default {
name: 'LessonsIndex'
}


import AppLayout from '@/Layouts/AppLayout.vue'
import { Link } from '@inertiajs/vue3'
import { Inertia } from '@inertiajs/inertia'

defineProps({
lessons: {
type: Object,
required: true
}
})

const deleteLesson = (id) => {
if(confirm('Are you sure?')) {
Inertia.delete(route('lessons.destroy', id))
}
}





Lessons








Create lesson












#


Name


Edit


Delete






{{ lesson.id }}


{{ lesson.name }}



Edit




Delete













PREV



NEXT








```
2. Crear vista **Lessons/Create**:
```html title="resources\js\Pages\Lessons\Create.vue"

export default {
name: 'LessonsCreate'
}


import AppLayout from '@/Layouts/AppLayout.vue'
import { useForm } from '@inertiajs/vue3'
import LessonForm from '@/Components/Lessons/Form.vue'

defineProps({
categories: {
type: Object,
required: true
},
levels: {
type: Object,
required: true
}
})

const form = useForm({
name: ''
})





Create lesson












```
3. Crear vista **Lessons/Edit**:
```html title=""
```
4. Crear componente **Components/Lessons/Form.vue**:
```html title="resources\js\Components\Lessons\Form.vue"

export default {
name: 'LessonsForm'
}


import FormSection from '@/Components/FormSection.vue'
import InputError from '@/Components/InputError.vue'
import InputLabel from '@/Components/InputLabel.vue'
import PrimaryButton from '@/Components/PrimaryButton.vue'
import TextInput from '@/Components/TextInput.vue'
import SecondaryButton from '@/Components/SecondaryButton.vue'
import CollectionSelector from '@/Components/Common/CollectionSelector.vue'
import { ref } from 'vue'

defineProps({
form: {
type: Object,
required: true
},
updating: {
type: Boolean,
required: false,
default: false
},
categories: {
type: Object,
required: true
},
levels: {
type: Object,
required: true
}
})

const categoriesSelected = ref([])

const onCategories = (_categories) => {
categoriesSelected.value = _categories
}

defineEmits(['submit'])




{{ updating ? 'Update Lesson' : 'Create Lesson' }}


{{ updating ? 'Update your lesson' : 'Create a new lesson' }}



















Upload PDF








{{ level.name }}












{{ updating ? 'Update' : 'Create' }}




```
5. Crear componente **Components/Common/CollectionSelector**:
```html title="resources\js\Components\Common\CollectionSelector.vue"

import { ref } from 'vue'

defineProps({
collection: {
type: Array,
required: true
}
})

const currentSelection = ref(1)
const selection = ref([])
const emit = defineEmits(['onCategories'])

const handleAddToSelection = () => {
let alreadyExists = false
selection.value.forEach((item) => {
if (item.id === currentSelection.value.id) {
alreadyExists = true
return
}
})
if (alreadyExists) {
return
}
selection.value.push(currentSelection.value)
emit('onCategories', selection.value)
}

const handleRemoveSelection = (index) => {
selection.value = selection.value.filter((item) => item.id !== index)
emit('onCategories', selection.value)
}





{{ item?.name }}


Add





  • {{ item.name }}
    X





```
13. Diseñar las vistas para los roles:
1. Crear vista **Roles/Index**:
```html title=""
```
2. Crear vista **Roles/Create**:
```html title=""
```
3. Crear vista **Roles/Edit**:
```html title=""
```
4. Crear componente **Components/Roles/Form.vue**:
```html title=""
```
14. mmmm