ADD: Plantilla Holos (#1)

This commit is contained in:
Moisés de Jesús Cortés Castellanos 2024-12-13 16:14:50 -06:00 committed by GitHub
parent 97723f5b5c
commit 517628b92d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
154 changed files with 11584 additions and 425 deletions

View File

@ -2,16 +2,23 @@ APP_NAME=Laravel
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_TIMEZONE=UTC APP_TIMEZONE=America/Mexico_City
APP_URL=http://localhost APP_URL=http://backend.holos.test
APP_FRONTEND_URL=http://frontend.holos.test
APP_PAGINATION=25
APP_LOCALE=en APP_LOCALE=es
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=es
APP_FAKER_LOCALE=en_US APP_FAKER_LOCALE=es_MX
APP_MAINTENANCE_DRIVER=file APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database # APP_MAINTENANCE_STORE=database
CORS_ALLOWED_ORIGINS=frontend.holos.test
PULSE_ENABLED=false
TELESCOPE_ENABLED=false
PHP_CLI_SERVER_WORKERS=4 PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12 BCRYPT_ROUNDS=12
@ -21,12 +28,12 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=sqlite DB_CONNECTION=mysql
# DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
# DB_PORT=3306 DB_PORT=3306
# DB_DATABASE=laravel DB_DATABASE=holos-backend
# DB_USERNAME=root DB_USERNAME=notsoweb
# DB_PASSWORD= DB_PASSWORD=
SESSION_DRIVER=database SESSION_DRIVER=database
SESSION_LIFETIME=120 SESSION_LIFETIME=120
@ -51,6 +58,7 @@ REDIS_PORT=6379
MAIL_MAILER=log MAIL_MAILER=log
MAIL_HOST=127.0.0.1 MAIL_HOST=127.0.0.1
MAIL_PORT=2525 MAIL_PORT=2525
MAIL_DOMAIN=notsoweb.com
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null
MAIL_ENCRYPTION=null MAIL_ENCRYPTION=null

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
/public/build /public/build
/public/hot /public/hot
/public/storage /public/storage
/public/vendor
/storage/*.key /storage/*.key
/storage/pail /storage/pail
/vendor /vendor

View File

@ -0,0 +1,48 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
/**
* Iniciar servicio de broadcast
*/
class Broadcast extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:broadcast
{--start : Iniciar servicio de broadcast}
{--stop : Detener servicio de broadcast}
{--restart : Reiniciar servicio de broadcast}
{--status : Mostrar estado del servicio de broadcast}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
if ($this->option('stop') || $this->option('restart')) {
echo "# Deteniendo servicio de broadcast... \n";
echo shell_exec('pm2 delete broadcast');
}
if ($this->option('start') || $this->option('restart')) {
echo "# Iniciando servicio de broadcast... \n";
echo shell_exec("pm2 start --name broadcast \"php artisan reverb:start --hostname=" . env('APP_URL') . "\"");
}
if ($this->option('status')) {
echo "# Estado del servicio de broadcast: \n";
echo shell_exec('pm2 status broadcast');
}
}
}

View File

@ -0,0 +1,57 @@
<?php namespace App\Console\Commands;
/**
* @copyright (c) 2024 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Ramsey\Uuid\Uuid;
/**
* Mantenimiento seguro
*
* De forma automática pondrá al sistema en modo mantenimiento con una URL secreta para
* poder acceder al sitio. El tiempo se puede configurar desde las variables de entorno.
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class DownSecure extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:secure';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Modo mantenimiento con un hash seguro';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$secret = Uuid::uuid4();
Artisan::call('down', [
'--secret' => $secret
]);
echo url($secret);
echo "\n";
Log::channel('notsoweb')->info("Maintenance Mode Secure. Key: {$secret}");
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,59 @@
<?php namespace App\Console\Commands;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Events\GlobalNotification;
use Illuminate\Console\Command;
/**
* Notificación global
*
* Notificación global a todos los usuarios conectados.
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class NotificationGlobal extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'notification:global
{--message=Notificación de prueba : Mensaje de la notificación}
{--title=Notificación Global : Título de la notificación}
{--type=info : Tipo de notificación (info, success, warning, error)}
{--timeout=15 : Tiempo de duración de la notificación}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Enviar notificación a todos los usuarios conectados';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$message = $this->option('message');
$title = $this->option('title');
$type = $this->option('type');
$timeout = $this->option('timeout');
broadcast(new GlobalNotification(
$title,
$message,
$type,
$timeout
));
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,55 @@
<?php namespace App\Events;
/**
* @copyright (c) 2024 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
/**
* Notificación global
*
* Notificación enviada a todos los usuarios conectados al canal Global.
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class GlobalNotification implements ShouldBroadcastNow
{
use Dispatchable,
InteractsWithBroadcasting,
InteractsWithSockets,
SerializesModels;
/**
* Constructor
*/
public function __construct(
public string $title,
public string $message,
public string $type = 'info',
public int $timeout = 15
) {}
/**
* Nombre del evento
*/
public function broadcastAs(): string
{
return 'App\Events\Notification';
}
/**
* Canal de envío
*/
public function broadcastOn(): Channel
{
return new PrivateChannel('Global');
}
}

View File

@ -1,7 +1,15 @@
<?php <?php namespace App\Http\Controllers;
/**
namespace App\Http\Controllers; * @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
/**
* Controlador base
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
abstract class Controller abstract class Controller
{ {
// //

View File

@ -0,0 +1,128 @@
<?php namespace App\Http\Controllers;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use App\Http\Requests\User\UserUpdateRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Notsoweb\LaravelCore\Supports\NotifySupport;
/**
* Usuario autenticado
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class MyUserController extends Controller
{
/**
* Obtener usuario autenticado
*/
public function show()
{
return ApiResponse::OK->response([
'user' => auth()->user()
]);
}
/**
* Actualizar
*/
public function update(UserUpdateRequest $request)
{
$form = $request->validated();
if (isset($form['photo'])) {
auth()->user()->updateProfilePhoto($form['photo']);
}
auth()->user()->update($form);
return ApiResponse::OK->response();
}
/**
* Eliminar
*/
public function destroy()
{
auth()->user()->delete();
return ApiResponse::OK->response();
}
/**
* Confirmar contraseña
*
* Autoriza una acción si la contraseña es correcta.
*/
public function confirmPassword(Request $request)
{
$form = $request->validate([
'password' => ['required', 'string', 'min:8']
]);
if (!Hash::check($form['password'], auth()->user()->password)) {
NotifySupport::errorIn('password', __('validation.password'));
}
return ApiResponse::OK->response();
}
/**
* Actualizar contraseña
*/
public function updatePassword(Request $request)
{
$form = $request->validate([
'current_password' => ['required', 'string'],
'password' => ['required', 'string', 'min:8', 'confirmed']
]);
if (!Hash::check($form['current_password'], auth()->user()->password)) {
NotifySupport::errorIn('current_password', __('validation.password'));
}
auth()->user()->update([
'password' => bcrypt($form['password'])
]);
return ApiResponse::OK->response();
}
/**
* Eliminar foto de perfil
*/
public function destroyPhoto()
{
auth()->user()->deleteProfilePhoto();
return ApiResponse::OK->response();
}
/**
* Permisos
*/
public function permissions()
{
return ApiResponse::OK->response([
'permissions' => auth()->user()->getAllPermissions()
]);
}
/**
* Roles
*/
public function roles()
{
return ApiResponse::OK->response([
'roles' => auth()->user()
->roles()
->select('id', 'name', 'description')
->get()
]);
}
}

View File

@ -0,0 +1,75 @@
<?php namespace App\Http\Controllers\System;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\User\ForgotRequest;
use App\Models\User;
use App\Notifications\ForgotPasswordNotification;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Notsoweb\ApiResponse\Enums\ApiResponse;
/**
* Controlador de sesiones
*
* @author Moisés Cortés C <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class LoginController extends Controller
{
/**
* Iniciar sesión
*/
public function login(LoginRequest $request)
{
return (Auth::attempt($request->all()))
? ApiResponse::OK->onSuccess([
'user' => auth()->user(),
'token' => auth()->user()
->createToken('golscore')
->accessToken,
])
: ApiResponse::UNPROCESSABLE_CONTENT->response([
'email' => ['Usuario no valido']
]);
}
/**
* Cerrar sesión
*/
public function logout()
{
return ApiResponse::OK->response([
'is_revoked' => auth()->user()->token()->revoke()
]);
}
/**
* Contraseña olvidada
*/
public function forgotPassword(ForgotRequest $request)
{
$data = $request->validated();
$user = User::where('email', $data['email'])->first();
try {
$user->notify(new ForgotPasswordNotification());
return ApiResponse::OK->response([
'is_sent' => true
]);
} catch (\Throwable $th) {
Log::channel('mail')->info("Email: {$data['email']}");
Log::channel('mail')->error($th->getMessage());
return ApiResponse::INTERNAL_ERROR->response([
'is_sent' => false,
]);
}
}
}

View File

@ -0,0 +1,95 @@
<?php namespace App\Http\Controllers\System;
/**
* @copyright 2024 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Notifications\DatabaseNotification;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Notsoweb\LaravelCore\Controllers\VueController;
/**
* Sistema de notificaciones
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class NotificationController extends VueController
{
/**
* Constructor
*/
public function __construct()
{
$this->root('notifications');
}
/**
* Listar notificaciones del usuario
*/
public function index()
{
$q = request()->get('query');
$model = auth()->user()
->notifications();
if($q) {
$model = $model->where('data->title', 'LIKE', "%{$q}%")
->orWhere('data->message', "LIKE", "%{$q}%");
}
return $this->view('index', [
'models' => $model
->paginate(config('app.pagination'))
]);
}
/**
* Todas las notificaciones
*/
public function all(): JsonResponse
{
$query = request()->get('query');
$model = auth()->user()
->notifications();
if($query) {
$model = $model->where('data->title', 'LIKE', "%{$query}%")
->orWhere('data->message', "LIKE", "%{$query}%");
}
return ApiResponse::OK->axios([
'notifications' => $model
->paginate(config('app.pagination'))
]);
}
/**
* Marcar notificación como leída
*/
public function read(Request $request): JsonResponse
{
$notification = DatabaseNotification::find($request->get('id'));
if ($notification) {
$notification->markAsRead();
}
return ApiResponse::OK->axios();
}
/**
* Obtener notificaciones no leídas recientes
*/
public function allUnread(): JsonResponse
{
return ApiResponse::OK->axios([
'total' => auth()->user()->unreadNotifications()->count(),
'notifications' => auth()->user()->unreadNotifications()->limit(10)->get(),
]);
}
}

View File

@ -0,0 +1,46 @@
<?php namespace App\Http\Controllers\System;
/**
* @copyright 2024 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Http\Controllers\Controller;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
/**
* Recursos del sistema
*
* Contiene determinados recursos que el sistema requiere para funcionar por parte del
* frontend.
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class SystemController extends Controller
{
/**
* Listar permisos del sistema
*/
public function permissions()
{
return ApiResponse::OK->response([
'permissions' => Permission::orderBy('name')
->select('id', 'name', 'description')
->get()
]);
}
/**
* Listar roles del sistema
*/
public function roles()
{
return ApiResponse::OK->response([
'roles' => Role::orderBy('description')
->select('id', 'name', 'description')
->get()
]);
}
}

View File

@ -0,0 +1,131 @@
<?php namespace App\Http\Controllers;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use App\Http\Controllers\Controller;
use App\Http\Requests\Users\PasswordUpdateRequest;
use App\Http\Requests\Users\UserStoreRequest;
use App\Http\Requests\Users\UserUpdateRequest;
use App\Models\User;
use App\Supports\QuerySupport;
use Illuminate\Http\Request;
use Notsoweb\ApiResponse\Enums\ApiResponse;
/**
* Controlador de usuarios
*
* Permite la administración de los usuarios en general.
*
* @author Moisés Cortés C <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class UserController extends Controller
{
/**
* Listar
*/
public function index()
{
$users = User::orderBy('name');
QuerySupport::queryByKeys($users, ['name', 'email']);
return ApiResponse::OK->response([
'users' => $users->paginate(config('app.pagination'))
]);
}
/**
* Almacenar
*/
public function store(UserStoreRequest $request)
{
$user = User::create($request->all());
if ($request->has('roles')) {
$user->roles()->sync($request->roles);
}
return ApiResponse::OK->response();
}
/**
* Mostrar
*/
public function show(User $user)
{
return ApiResponse::OK->response([
'user' => $user
]);
}
/**
* Actualizar
*/
public function update(UserUpdateRequest $request, User $user)
{
$user->update($request->all());
return ApiResponse::OK->response();
}
/**
* Eliminar
*/
public function destroy(User $user)
{
$user->delete();
return ApiResponse::OK->response();
}
/**
* Permisos del usuario
*/
public function permissions(User $user)
{
return ApiResponse::OK->response([
'permissions' => $user->getAllPermissions()
]);
}
/**
* Roles del usuario
*/
public function roles(User $user)
{
return ApiResponse::OK->response([
'roles' => $user
->roles()
->select('id', 'name', 'description')
->get()
]);
}
/**
* Actualizar roles
*/
public function updateRoles(Request $request, User $user)
{
if ($request->has('roles')) {
$user->roles()->sync($request->roles);
}
return ApiResponse::OK->response();
}
/**
* Actualizar contraseña
*/
public function updatePassword(PasswordUpdateRequest $request, User $user)
{
$user->update([
'password' => bcrypt($request->password)
]);
return ApiResponse::OK->response();
}
}

View File

@ -0,0 +1,37 @@
<?php namespace App\Http\Requests\Auth;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Solicitud de login
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'email'],
'password' => ['required', 'min:8'],
];
}
}

View File

@ -0,0 +1,34 @@
<?php namespace App\Http\Requests\User;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Solicitud de olvido de contraseña
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class ForgotRequest extends FormRequest
{
/**
* Determinar si el usuario está autorizado para realizar esta solicitud
*/
public function authorize(): bool
{
return true;
}
/**
* Obtener las reglas de validación que se aplican a la solicitud
*/
public function rules(): array
{
return [
'email' => ['required', 'email', 'exists:users,email']
];
}
}

View File

@ -0,0 +1,46 @@
<?php namespace App\Http\Requests\User;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/**
* Actualizar perfil usuario actual
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class UserUpdateRequest extends FormRequest
{
/**
* Determinar si el usuario está autorizado para realizar esta solicitud
*/
public function authorize(): bool
{
return true;
}
/**
* Obtener las reglas de validación que se aplican a la solicitud
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'paternal' => ['required', 'string', 'max:255'],
'maternal' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore(auth()->user()->id),
],
'phone' => ['nullable', 'numeric', 'digits:10'],
'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:2048'],
];
}
}

View File

@ -0,0 +1,34 @@
<?php namespace App\Http\Requests\Users;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Actualizar contraseña desde gestión de usuarios
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class PasswordUpdateRequest extends FormRequest
{
/**
* Determinar si el usuario está autorizado para realizar esta solicitud
*/
public function authorize(): bool
{
return true;
}
/**
* Obtener las reglas de validación que se aplican a la solicitud
*/
public function rules(): array
{
return [
'password' => ['required', 'string', 'min:8', 'confirmed']
];
}
}

View File

@ -0,0 +1,42 @@
<?php namespace App\Http\Requests\Users;
/**
* @copyright (C) 2024 Notsoweb Software (https://notsoweb.com) - All rights reserved
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Almacenar usuario
*
* @author Moisés Cortés C <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class UserStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'paternal' => ['required', 'string', 'max:255'],
'maternal' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'phone' => ['nullable', 'numeric', 'digits:10'],
'password' => ['required', 'string', 'min:8'],
'roles' => ['nullable', 'array']
];
}
}

View File

@ -0,0 +1,42 @@
<?php namespace App\Http\Requests\Users;
/**
* @copyright (C) 2024 Notsoweb Software (https://notsoweb.com) - All rights reserved
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/**
* Actualizar usuario
*
* @author Moisés Cortés C <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class UserUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'paternal' => ['required', 'string', 'max:255'],
'maternal' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($this->route('user'))],
'phone' => ['nullable', 'numeric', 'digits:10'],
'roles' => ['nullable', 'array']
];
}
}

View File

@ -0,0 +1,86 @@
<?php namespace App\Http\Traits;
/**
* @copyright (C) 2024 Notsoweb Software (https://notsoweb.com) - All rights reserved
*/
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
/**
* Trait para manejar la foto de perfil
*
* @author Moisés Cortés C <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
trait HasProfilePhoto
{
/**
* Actualizar la foto de perfil del usuario
*/
public function updateProfilePhoto(UploadedFile $photo, $storagePath = 'photos') : void
{
tap($this->profile_photo_path, function ($previous) use ($photo, $storagePath) {
$this->forceFill([
'profile_photo_path' => $photo->storePublicly(
$storagePath, ['disk' => $this->profilePhotoDisk()]
),
])->save();
if ($previous) {
Storage::disk($this->profilePhotoDisk())->delete($previous);
}
});
}
/**
* Eliminar la foto de perfil del usuario
*/
public function deleteProfilePhoto() : void
{
if (is_null($this->profile_photo_path)) {
return;
}
Storage::disk($this->profilePhotoDisk())->delete($this->profile_photo_path);
$this->forceFill([
'profile_photo_path' => null,
])->save();
}
/**
* Obtener la URL de la foto de perfil del usuario
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function profilePhotoUrl(): Attribute
{
return Attribute::get(function (): string {
return $this->profile_photo_path
? Storage::disk($this->profilePhotoDisk())->url($this->profile_photo_path)
: $this->defaultProfilePhotoUrl();
});
}
/**
* Obtener la URL de la foto de perfil por defecto si no se ha subido ninguna
*/
protected function defaultProfilePhotoUrl() : string
{
$name = trim(collect(explode(' ', $this->name))->map(function ($segment) {
return mb_substr($segment, 0, 1);
})->join(' '));
return 'https://ui-avatars.com/api/?name='.urlencode($name).'&color=7F9CF5&background=EBF4FF';
}
/**
* Obtener el disco donde se deben almacenar las fotos de perfil
*/
protected function profilePhotoDisk() : string
{
return 'profile';
}
}

View File

@ -0,0 +1,24 @@
<?php namespace App\Models;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Database\Eloquent\Model;
/**
* Tipos de permisos
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class PermissionType extends Model
{
/**
* Atributos permitidos
*/
protected $fillable = [
'name',
'description'
];
}

View File

@ -0,0 +1,25 @@
<?php namespace App\Models;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Database\Eloquent\Model;
/**
* Modelo de contraseñas olvidadas
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class ResetPassword extends Model
{
/**
* Atributos asignables
*/
protected $fillable = [
'email',
'token',
'created_at',
];
}

View File

@ -1,32 +1,46 @@
<?php <?php namespace App\Models;
/**
namespace App\Models; * @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Http\Traits\HasProfilePhoto;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
/**
* Modelo de usuario
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<\Database\Factories\UserFactory> */ use HasApiTokens,
use HasFactory, Notifiable; HasFactory,
HasRoles,
HasProfilePhoto,
Notifiable;
/** /**
* The attributes that are mass assignable. * Atributos permitidos
*
* @var array<int, string>
*/ */
protected $fillable = [ protected $fillable = [
'name', 'name',
'paternal',
'maternal',
'email', 'email',
'phone',
'password', 'password',
'profile_photo_path',
]; ];
/** /**
* The attributes that should be hidden for serialization. * Atributos ocultos
*
* @var array<int, string>
*/ */
protected $hidden = [ protected $hidden = [
'password', 'password',
@ -34,9 +48,7 @@ class User extends Authenticatable
]; ];
/** /**
* Get the attributes that should be cast. * Atributos que se deben convertir
*
* @return array<string, string>
*/ */
protected function casts(): array protected function casts(): array
{ {
@ -45,4 +57,11 @@ protected function casts(): array
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
/**
* Los accesores a añadir al modelo en su forma de array
*/
protected $appends = [
'profile_photo_url',
];
} }

View File

@ -0,0 +1,57 @@
<?php namespace App\Notifications;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
/**
* Descripción
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class ForgotPasswordNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*/
public function __construct()
{
//
}
/**
* Obtener los canales de entrega de la notificación
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Obtener la representación del mensaje de la notificación
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(__('auth.forgot-password.subject'))
->markdown('user.password-forgot');
}
/**
* Obtener la representación en array de la notificación
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}

View File

@ -0,0 +1,62 @@
<?php namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Notifications\Notification;
class UserNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*/
public function __construct(
public string $title,
public string $description,
public ?string $message = null,
public string $type = 'info',
public int $timeout = 20,
) {}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return [
'broadcast',
'database'
];
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
'title' => $this->title,
'description' => $this->description,
'message' => $this->message,
'type' => $this->type,
];
}
/**
* Transmitir notificación
*/
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'title' => $this->title,
'description' => $this->description,
'typeNotification' => $this->type,
'timeout' => $this->timeout,
]);
}
}

View File

@ -1,17 +1,31 @@
<?php <?php namespace App\Providers;
/**
namespace App\Providers; * @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;
/**
* Proveedor de servicios de la aplicación
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
/** /**
* Register any application services. * Registrar cualquier servicio de la aplicación
*/ */
public function register(): void public function register(): void
{ {
// if ($this->app->environment('local')) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
$this->app->register(TelescopeServiceProvider::class);
}
} }
/** /**
@ -19,6 +33,14 @@ public function register(): void
*/ */
public function boot(): void public function boot(): void
{ {
// Passport::loadKeysFrom(storage_path('app/keys'));
Passport::tokensExpireIn(now()->addDays(30));
Passport::refreshTokensExpireIn(now()->addDays(60));
Passport::personalAccessTokensExpireIn(now()->addMonths(12));
// Acceso a Pulse
Gate::define('viewPulse', function (User $user) {
return $user->hasRole('developer');
});
} }
} }

View File

@ -0,0 +1,71 @@
<?php namespace App\Providers;
/**
* @copyright (c) 2024 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
/**
* Proveedor de servicios de Telescope
*
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
*
* @version 1.0.0
*/
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Registrar cualquier servicio de la aplicación
*/
public function register(): void
{
Telescope::night();
$this->hideSensitiveRequestDetails();
$isLocal = $this->app->environment('local');
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
return $isLocal ||
$entry->isReportableException() ||
$entry->isFailedRequest() ||
$entry->isFailedJob() ||
$entry->isScheduledTask() ||
$entry->hasMonitoredTag();
});
}
/**
* Prevenir que los detalles de las solicitudes sensibles se registren en Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Registrar la puerta de acceso de Telescope.
*
* Esta puerta determina quién puede acceder a Telescope en entornos no locales.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function (User $user) {
return $user->hasRole('developer');
});
}
}

View File

@ -0,0 +1,23 @@
<?php namespace App\Schedules;
/**
* @copyright (c) 2024 Notsoweb Software (https://www.notsoweb.com) - All rights reserved.
*/
use App\Models\ResetPassword;
use Illuminate\Support\Carbon;
/**
* Eliminar tokens de reseteos de contraseñas
*
* @author Moisés Cortés C <moises.cortes@notsoweb.com>
*/
class DeleteResetPasswords
{
/**
* Manipulador
*/
public function __invoke()
{
ResetPassword::where('created_at', '<', Carbon::now()->subMinutes(10))->delete();
}
}

View File

@ -0,0 +1,64 @@
<?php namespace App\Supports;
/**
* @copyright Copyright (c) 2024 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
/**
* Facilita algunas consultas de búsqueda
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class QuerySupport
{
/**
* Realiza una búsqueda por medio de una clave
*/
public static function queryByKey(&$model, $key = 'name')
{
$query = request()->get('q');
if ($query) {
$model = $model->where($key, $query);
}
}
/**
* Realiza una búsqueda por medio de una clave e incluye relaciones anidadas
*/
public static function queryByKeys(&$model, array $keys = ['name'])
{
$query = request()->get('q');
if ($query) {
$model = $model->where(function ($x) use ($query, $keys) {
$count = 0;
foreach ($keys as $key) {
if (strpos($key, '.') !== false) {
// Si la clave contiene un punto, asumimos que es una relación anidada
list($relation, $relationKey) = explode('.', $key);
$x = ($count == 0)
? $x->whereHas($relation, function ($q) use ($query, $relationKey) {
$q->where($relationKey, 'LIKE', "%{$query}%");
})
: $x->orWhereHas($relation, function ($q) use ($query, $relationKey) {
$q->where($relationKey, 'LIKE', "%{$query}%");
});
} else {
$x = ($count == 0)
? $x->where($key, 'LIKE', "%{$query}%")
: $x->orWhere($key, 'LIKE', "%{$query}%");
}
$count++;
}
return $x;
});
}
}
}

View File

@ -6,12 +6,25 @@
return Application::configure(basePath: dirname(__DIR__)) return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
api: __DIR__.'/../routes/api.php',
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
health: '/up', health: '/up',
) )
->withBroadcasting(
channels: __DIR__.'/../routes/channels.php',
attributes: ['middleware' => ['auth:api']]
)
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {
// $middleware->validateCsrfTokens(except: [
'sanctum/csrf-cookie',
'user/*'
]);
$middleware->alias([
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
}) })
->withExceptions(function (Exceptions $exceptions) { ->withExceptions(function (Exceptions $exceptions) {
// //

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

View File

@ -2,4 +2,6 @@
return [ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
Notsoweb\LaravelCore\ServiceProvider::class,
Spatie\Permission\PermissionServiceProvider::class,
]; ];

View File

@ -8,7 +8,12 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"laravel/framework": "^11.31", "laravel/framework": "^11.31",
"laravel/passport": "^12.0",
"laravel/pulse": "^1.2",
"laravel/reverb": "^1.0",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"notsoweb/laravel-core": "dev-main",
"spatie/laravel-permission": "^6.10",
"tightenco/ziggy": "^2.4" "tightenco/ziggy": "^2.4"
}, },
"require-dev": { "require-dev": {
@ -16,6 +21,7 @@
"laravel/pail": "^1.1", "laravel/pail": "^1.1",
"laravel/pint": "^1.13", "laravel/pint": "^1.13",
"laravel/sail": "^1.26", "laravel/sail": "^1.26",
"laravel/telescope": "^5.2",
"mockery/mockery": "^1.6", "mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1", "nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^11.0.1" "phpunit/phpunit": "^11.0.1"
@ -51,11 +57,54 @@
"dev": [ "dev": [
"Composer\\Config::disableProcessTimeout", "Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
],
"env:dev": [
"composer install",
"composer run post-root-package-install",
"composer run post-create-project-cmd",
"@php artisan storage:link"
],
"env:prod": [
"composer install --no-dev",
"composer run post-root-package-install",
"composer run post-create-project-cmd",
"@php artisan storage:link"
],
"db:dev": [
"@php artisan migrate:fresh --seeder=DevSeeder",
"@php artisan passport:client --personal --name=Holos"
],
"db:prod": [
"@php artisan migrate:fresh --seed",
"@php artisan passport:client --personal --name=Holos"
],
"jobs:start": [
"pm2 start \"php artisan schedule:work\" --name holos-schedules",
"pm2 start \"php artisan queue:work\" --name holos-queue"
],
"jobs:stop": [
"pm2 delete holos-schedules",
"pm2 delete holos-queue"
],
"jobs:status": [
"pm2 show holos-schedules",
"pm2 show holos-queue"
],
"broadcast:start": [
"pm2 start \"php artisan reverb:start\" --name=holos-broadcasts"
],
"broadcast:stop": [
"pm2 delete holos-broadcasts"
],
"broadcast:status": [
"pm2 show holos-broadcasts"
] ]
}, },
"extra": { "extra": {
"laravel": { "laravel": {
"dont-discover": [] "dont-discover": [
"laravel/telescope"
]
} }
}, },
"config": { "config": {
@ -67,6 +116,6 @@
"php-http/discovery": true "php-http/discovery": true
} }
}, },
"minimum-stability": "stable", "minimum-stability": "dev",
"prefer-stable": true "prefer-stable": true
} }

2952
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,9 +12,12 @@
| other UI elements where an application name needs to be displayed. | other UI elements where an application name needs to be displayed.
| |
*/ */
'version' => '0.0.1',
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'Laravel'),
'pagination' => env('APP_PAGINATION', 25),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Environment | Application Environment
@ -122,5 +125,4 @@
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'), 'store' => env('APP_MAINTENANCE_STORE', 'database'),
], ],
]; ];

View File

@ -40,6 +40,10 @@
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', 'provider' => 'users',
], ],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
], ],
/* /*
@ -64,11 +68,6 @@
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class), 'model' => env('AUTH_MODEL', App\Models\User::class),
], ],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
], ],
/* /*

82
config/broadcasting.php Normal file
View File

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

33
config/cors.php Normal file
View File

@ -0,0 +1,33 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie', 'user/*', 'broadcasting/auth'],
'allowed_methods' => ['*'],
'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS')),
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];

View File

@ -45,6 +45,14 @@
'throw' => false, 'throw' => false,
], ],
'profile' => [
'driver' => 'local',
'root' => storage_path('app/profile'),
'url' => env('APP_URL').'/profile',
'visibility' => 'public',
'throw' => false,
],
's3' => [ 's3' => [
'driver' => 's3', 'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'), 'key' => env('AWS_ACCESS_KEY_ID'),
@ -56,7 +64,6 @@
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false, 'throw' => false,
], ],
], ],
/* /*
@ -72,6 +79,6 @@
'links' => [ 'links' => [
public_path('storage') => storage_path('app/public'), public_path('storage') => storage_path('app/public'),
public_path('profile') => storage_path('app/profile'),
], ],
]; ];

View File

@ -51,7 +51,6 @@
*/ */
'channels' => [ 'channels' => [
'stack' => [ 'stack' => [
'driver' => 'stack', 'driver' => 'stack',
'channels' => explode(',', env('LOG_STACK', 'single')), 'channels' => explode(',', env('LOG_STACK', 'single')),
@ -127,6 +126,9 @@
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
], ],
'mail' => [
'driver' => 'single',
'path' => storage_path('logs/mail.log'),
],
], ],
]; ];

View File

@ -2,6 +2,8 @@
return [ return [
'domain' => env('MAIL_DOMAIN', 'notsoweb.com'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Default Mailer | Default Mailer

75
config/passport.php Normal file
View File

@ -0,0 +1,75 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Passport Guard
|--------------------------------------------------------------------------
|
| Here you may specify which authentication guard Passport will use when
| authenticating users. This value should correspond with one of your
| guards that is already present in your "auth" configuration file.
|
*/
'guard' => 'web',
/*
|--------------------------------------------------------------------------
| Encryption Keys
|--------------------------------------------------------------------------
|
| Passport uses encryption keys while generating secure access tokens for
| your application. By default, the keys are stored as local files but
| can be set via environment variables when that is more convenient.
|
*/
'private_key' => env('PASSPORT_PRIVATE_KEY'),
'public_key' => env('PASSPORT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Passport Database Connection
|--------------------------------------------------------------------------
|
| By default, Passport's models will utilize your application's default
| database connection. If you wish to use a different connection you
| may specify the configured name of the database connection here.
|
*/
'connection' => env('PASSPORT_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Client UUIDs
|--------------------------------------------------------------------------
|
| By default, Passport uses auto-incrementing primary keys when assigning
| IDs to clients. However, if Passport is installed using the provided
| --uuids switch, this will be set to "true" and UUIDs will be used.
|
*/
'client_uuids' => true,
/*
|--------------------------------------------------------------------------
| Personal Access Client
|--------------------------------------------------------------------------
|
| If you enable client hashing, you should set the personal access client
| ID and unhashed secret within your environment file. The values will
| get used while issuing fresh personal access tokens to your users.
|
*/
'personal_access_client' => [
'id' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_ID'),
'secret' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET'),
],
];

186
config/permission.php Normal file
View File

@ -0,0 +1,186 @@
<?php
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related pivots other than defaults
*/
'role_pivot_key' => null, //default 'role_id',
'permission_pivot_key' => null, //default 'permission_id',
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
/*
* Change this if you want to use the teams feature and your related model's
* foreign key is other than `team_id`.
*/
'team_foreign_key' => 'team_id',
],
/*
* When set to true, the method for checking permissions will be registered on the gate.
* Set this to false if you want to implement custom logic for checking permissions.
*/
'register_permission_check_method' => true,
/*
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
*/
'register_octane_reset_listener' => false,
/*
* Teams Feature.
* When set to true the package implements teams using the 'team_foreign_key'.
* If you want the migrations to register the 'team_foreign_key', you must
* set this to true before doing the migration.
* If you already did the migration then you must make a new migration to also
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
* (view the latest version of this package's migration file)
*/
'teams' => false,
/*
* Passport Client Credentials Grant
* When set to true the package will use Passports Client to check permissions
*/
'use_passport_client_credentials' => false,
/*
* When set to true, the required permission names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
/*
* When set to true, the required role names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_role_in_exception' => false,
/*
* By default wildcard permission lookups are disabled.
* See documentation to understand supported syntax.
*/
'enable_wildcard_permission' => false,
/*
* The class to use for interpreting wildcard permissions.
* If you need to modify delimiters, override the class and specify its name here.
*/
// 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class,
/* Cache-specific settings */
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

232
config/pulse.php Normal file
View File

@ -0,0 +1,232 @@
<?php
use Laravel\Pulse\Http\Middleware\Authorize;
use Laravel\Pulse\Pulse;
use Laravel\Pulse\Recorders;
return [
/*
|--------------------------------------------------------------------------
| Pulse Domain
|--------------------------------------------------------------------------
|
| This is the subdomain which the Pulse dashboard will be accessible from.
| When set to null, the dashboard will reside under the same domain as
| the application. Remember to configure your DNS entries correctly.
|
*/
'domain' => env('PULSE_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Pulse Path
|--------------------------------------------------------------------------
|
| This is the path which the Pulse dashboard will be accessible from. Feel
| free to change this path to anything you'd like. Note that this won't
| affect the path of the internal API that is never exposed to users.
|
*/
'path' => env('PULSE_PATH', 'pulse'),
/*
|--------------------------------------------------------------------------
| Pulse Master Switch
|--------------------------------------------------------------------------
|
| This configuration option may be used to completely disable all Pulse
| data recorders regardless of their individual configurations. This
| provides a single option to quickly disable all Pulse recording.
|
*/
'enabled' => env('PULSE_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Pulse Storage Driver
|--------------------------------------------------------------------------
|
| This configuration option determines which storage driver will be used
| while storing entries from Pulse's recorders. In addition, you also
| may provide any options to configure the selected storage driver.
|
*/
'storage' => [
'driver' => env('PULSE_STORAGE_DRIVER', 'database'),
'database' => [
'connection' => env('PULSE_DB_CONNECTION'),
'chunk' => 1000,
],
],
/*
|--------------------------------------------------------------------------
| Pulse Ingest Driver
|--------------------------------------------------------------------------
|
| This configuration options determines the ingest driver that will be used
| to capture entries from Pulse's recorders. Ingest drivers are great to
| free up your request workers quickly by offloading the data storage.
|
*/
'ingest' => [
'driver' => env('PULSE_INGEST_DRIVER', 'storage'),
'buffer' => env('PULSE_INGEST_BUFFER', 5_000),
'trim' => [
'lottery' => [1, 1_000],
'keep' => '7 days',
],
'redis' => [
'connection' => env('PULSE_REDIS_CONNECTION'),
'chunk' => 1000,
],
],
/*
|--------------------------------------------------------------------------
| Pulse Cache Driver
|--------------------------------------------------------------------------
|
| This configuration option determines the cache driver that will be used
| for various tasks, including caching dashboard results, establishing
| locks for events that should only occur on one server and signals.
|
*/
'cache' => env('PULSE_CACHE_DRIVER'),
/*
|--------------------------------------------------------------------------
| Pulse Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will be assigned to every Pulse route, giving you the
| chance to add your own middleware to this list or change any of the
| existing middleware. Of course, reasonable defaults are provided.
|
*/
'middleware' => [
'web',
Authorize::class,
],
/*
|--------------------------------------------------------------------------
| Pulse Recorders
|--------------------------------------------------------------------------
|
| The following array lists the "recorders" that will be registered with
| Pulse, along with their configuration. Recorders gather application
| event data from requests and tasks to pass to your ingest driver.
|
*/
'recorders' => [
Recorders\CacheInteractions::class => [
'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true),
'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1),
'ignore' => [
...Pulse::defaultVendorCacheKeys(),
],
'groups' => [
'/^job-exceptions:.*/' => 'job-exceptions:*',
// '/:\d+/' => ':*',
],
],
Recorders\Exceptions::class => [
'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true),
'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1),
'location' => env('PULSE_EXCEPTIONS_LOCATION', true),
'ignore' => [
// '/^Package\\\\Exceptions\\\\/',
],
],
Recorders\Queues::class => [
'enabled' => env('PULSE_QUEUES_ENABLED', true),
'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1),
'ignore' => [
// '/^Package\\\\Jobs\\\\/',
],
],
Recorders\Servers::class => [
'server_name' => env('PULSE_SERVER_NAME', gethostname()),
'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')),
],
Recorders\SlowJobs::class => [
'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_JOBS_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000),
'ignore' => [
// '/^Package\\\\Jobs\\\\/',
],
],
Recorders\SlowOutgoingRequests::class => [
'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_OUTGOING_REQUESTS_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000),
'ignore' => [
// '#^http://127\.0\.0\.1:13714#', // Inertia SSR...
],
'groups' => [
// '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*',
// '#^https?://([^/]*).*$#' => '\1',
// '#/\d+#' => '/*',
],
],
Recorders\SlowQueries::class => [
'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_QUERIES_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000),
'location' => env('PULSE_SLOW_QUERIES_LOCATION', true),
'max_query_length' => env('PULSE_SLOW_QUERIES_MAX_QUERY_LENGTH'),
'ignore' => [
'/(["`])pulse_[\w]+?\1/', // Pulse tables...
'/(["`])telescope_[\w]+?\1/', // Telescope tables...
],
],
Recorders\SlowRequests::class => [
'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_REQUESTS_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000),
'ignore' => [
'#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard...
'#^/telescope#', // Telescope dashboard...
],
],
Recorders\UserJobs::class => [
'enabled' => env('PULSE_USER_JOBS_ENABLED', true),
'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1),
'ignore' => [
// '/^Package\\\\Jobs\\\\/',
],
],
Recorders\UserRequests::class => [
'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1),
'ignore' => [
'#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard...
'#^/telescope#', // Telescope dashboard...
],
],
],
];

92
config/reverb.php Normal file
View File

@ -0,0 +1,92 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Reverb Server
|--------------------------------------------------------------------------
|
| This option controls the default server used by Reverb to handle
| incoming messages as well as broadcasting message to all your
| connected clients. At this time only "reverb" is supported.
|
*/
'default' => env('REVERB_SERVER', 'reverb'),
/*
|--------------------------------------------------------------------------
| Reverb Servers
|--------------------------------------------------------------------------
|
| Here you may define details for each of the supported Reverb servers.
| Each server has its own configuration options that are defined in
| the array below. You should ensure all the options are present.
|
*/
'servers' => [
'reverb' => [
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
'port' => env('REVERB_SERVER_PORT', 8080),
'hostname' => env('REVERB_HOST'),
'options' => [
'tls' => [],
],
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
'scaling' => [
'enabled' => env('REVERB_SCALING_ENABLED', false),
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
'server' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', '6379'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DB', '0'),
],
],
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
],
],
/*
|--------------------------------------------------------------------------
| Reverb Applications
|--------------------------------------------------------------------------
|
| Here you may define how Reverb applications are managed. If you choose
| to use the "config" provider, you may define an array of apps which
| your server will support, including their connection credentials.
|
*/
'apps' => [
'provider' => 'config',
'apps' => [
[
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST'),
'port' => env('REVERB_PORT', 443),
'scheme' => env('REVERB_SCHEME', 'https'),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
],
'allowed_origins' => ['*'],
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
],
],
],
];

205
config/telescope.php Normal file
View File

@ -0,0 +1,205 @@
<?php
use Laravel\Telescope\Http\Middleware\Authorize;
use Laravel\Telescope\Watchers;
return [
/*
|--------------------------------------------------------------------------
| Telescope Master Switch
|--------------------------------------------------------------------------
|
| This option may be used to disable all Telescope watchers regardless
| of their individual configuration, which simply provides a single
| and convenient way to enable or disable Telescope data storage.
|
*/
'enabled' => env('TELESCOPE_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Telescope Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Telescope will be accessible from. If the
| setting is null, Telescope will reside under the same domain as the
| application. Otherwise, this value will be used as the subdomain.
|
*/
'domain' => env('TELESCOPE_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Telescope Path
|--------------------------------------------------------------------------
|
| This is the URI path where Telescope will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => env('TELESCOPE_PATH', 'telescope'),
/*
|--------------------------------------------------------------------------
| Telescope Storage Driver
|--------------------------------------------------------------------------
|
| This configuration options determines the storage driver that will
| be used to store Telescope's data. In addition, you may set any
| custom options as needed by the particular driver you choose.
|
*/
'driver' => env('TELESCOPE_DRIVER', 'database'),
'storage' => [
'database' => [
'connection' => env('DB_CONNECTION', 'mysql'),
'chunk' => 1000,
],
],
/*
|--------------------------------------------------------------------------
| Telescope Queue
|--------------------------------------------------------------------------
|
| This configuration options determines the queue connection and queue
| which will be used to process ProcessPendingUpdate jobs. This can
| be changed if you would prefer to use a non-default connection.
|
*/
'queue' => [
'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
'queue' => env('TELESCOPE_QUEUE', null),
],
/*
|--------------------------------------------------------------------------
| Telescope Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will be assigned to every Telescope route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => [
'web',
Authorize::class,
],
/*
|--------------------------------------------------------------------------
| Allowed / Ignored Paths & Commands
|--------------------------------------------------------------------------
|
| The following array lists the URI paths and Artisan commands that will
| not be watched by Telescope. In addition to this list, some Laravel
| commands, like migrations and queue commands, are always ignored.
|
*/
'only_paths' => [
// 'api/*'
],
'ignore_paths' => [
'livewire*',
'nova-api*',
'pulse*',
],
'ignore_commands' => [
//
],
/*
|--------------------------------------------------------------------------
| Telescope Watchers
|--------------------------------------------------------------------------
|
| The following array lists the "watchers" that will be registered with
| Telescope. The watchers gather the application's profile data when
| a request or task is executed. Feel free to customize this list.
|
*/
'watchers' => [
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
Watchers\CacheWatcher::class => [
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
'hidden' => [],
],
Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
Watchers\CommandWatcher::class => [
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
'ignore' => [],
],
Watchers\DumpWatcher::class => [
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
],
Watchers\EventWatcher::class => [
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
'ignore' => [],
],
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
Watchers\GateWatcher::class => [
'enabled' => env('TELESCOPE_GATE_WATCHER', true),
'ignore_abilities' => [],
'ignore_packages' => true,
'ignore_paths' => [],
],
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true),
Watchers\LogWatcher::class => [
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
'level' => 'error',
],
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true),
Watchers\ModelWatcher::class => [
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
'events' => ['eloquent.*'],
'hydrations' => true,
],
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true),
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'ignore_packages' => true,
'ignore_paths' => [],
'slow' => 100,
],
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
Watchers\RequestWatcher::class => [
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
'ignore_http_methods' => [],
'ignore_status_codes' => [],
],
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true),
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
],
];

View File

@ -14,10 +14,15 @@ public function up(): void
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('paternal');
$table->string('maternal')->nullable();
$table->string('phone')->nullable();
$table->string('email')->unique(); $table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable(); $table->timestamp('email_verified_at')->nullable();
$table->string('password'); $table->string('password');
$table->rememberToken(); $table->rememberToken();
$table->foreignId('current_team_id')->nullable();
$table->string('profile_photo_path', 2048)->nullable();
$table->timestamps(); $table->timestamps();
}); });

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_auth_codes', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->unsignedBigInteger('user_id')->index();
$table->uuid('client_id');
$table->text('scopes')->nullable();
$table->boolean('revoked');
$table->dateTime('expires_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_auth_codes');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_access_tokens', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->uuid('client_id');
$table->string('name')->nullable();
$table->text('scopes')->nullable();
$table->boolean('revoked');
$table->timestamps();
$table->dateTime('expires_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_access_tokens');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_refresh_tokens', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->string('access_token_id', 100)->index();
$table->boolean('revoked');
$table->dateTime('expires_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_refresh_tokens');
}
};

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_clients', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->string('name');
$table->string('secret', 100)->nullable();
$table->string('provider')->nullable();
$table->text('redirect');
$table->boolean('personal_access_client');
$table->boolean('password_client');
$table->boolean('revoked');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_clients');
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_personal_access_clients', function (Blueprint $table) {
$table->bigIncrements('id');
$table->uuid('client_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_personal_access_clients');
}
};

View File

@ -0,0 +1,84 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Laravel\Pulse\Support\PulseMigration;
return new class extends PulseMigration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (! $this->shouldRun()) {
return;
}
Schema::create('pulse_values', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('timestamp');
$table->string('type');
$table->mediumText('key');
match ($this->driver()) {
'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'),
'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'),
'sqlite' => $table->string('key_hash'),
};
$table->mediumText('value');
$table->index('timestamp'); // For trimming...
$table->index('type'); // For fast lookups and purging...
$table->unique(['type', 'key_hash']); // For data integrity and upserts...
});
Schema::create('pulse_entries', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('timestamp');
$table->string('type');
$table->mediumText('key');
match ($this->driver()) {
'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'),
'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'),
'sqlite' => $table->string('key_hash'),
};
$table->bigInteger('value')->nullable();
$table->index('timestamp'); // For trimming...
$table->index('type'); // For purging...
$table->index('key_hash'); // For mapping...
$table->index(['timestamp', 'type', 'key_hash', 'value']); // For aggregate queries...
});
Schema::create('pulse_aggregates', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('bucket');
$table->unsignedMediumInteger('period');
$table->string('type');
$table->mediumText('key');
match ($this->driver()) {
'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'),
'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'),
'sqlite' => $table->string('key_hash'),
};
$table->string('aggregate');
$table->decimal('value', 20, 2);
$table->unsignedInteger('count')->nullable();
$table->unique(['bucket', 'period', 'type', 'aggregate', 'key_hash']); // Force "on duplicate update"...
$table->index(['period', 'bucket']); // For trimming...
$table->index('type'); // For purging...
$table->index(['period', 'type', 'aggregate', 'bucket']); // For aggregate queries...
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('pulse_values');
Schema::dropIfExists('pulse_entries');
Schema::dropIfExists('pulse_aggregates');
}
};

View File

@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Get the migration connection name.
*/
public function getConnection(): ?string
{
return config('telescope.storage.database.connection');
}
/**
* Run the migrations.
*/
public function up(): void
{
$schema = Schema::connection($this->getConnection());
$schema->create('telescope_entries', function (Blueprint $table) {
$table->bigIncrements('sequence');
$table->uuid('uuid');
$table->uuid('batch_id');
$table->string('family_hash')->nullable();
$table->boolean('should_display_on_index')->default(true);
$table->string('type', 20);
$table->longText('content');
$table->dateTime('created_at')->nullable();
$table->unique('uuid');
$table->index('batch_id');
$table->index('family_hash');
$table->index('created_at');
$table->index(['type', 'should_display_on_index']);
});
$schema->create('telescope_entries_tags', function (Blueprint $table) {
$table->uuid('entry_uuid');
$table->string('tag');
$table->primary(['entry_uuid', 'tag']);
$table->index('tag');
$table->foreign('entry_uuid')
->references('uuid')
->on('telescope_entries')
->onDelete('cascade');
});
$schema->create('telescope_monitoring', function (Blueprint $table) {
$table->string('tag')->primary();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$schema = Schema::connection($this->getConnection());
$schema->dropIfExists('telescope_entries_tags');
$schema->dropIfExists('telescope_entries');
$schema->dropIfExists('telescope_monitoring');
}
};

View File

@ -0,0 +1,166 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('permission_types', function (Blueprint $table) {
$table->id('id');
$table->string('name');
$table->string('description')->nullable();
$table->timestamps();
});
// Tabla de equipos
$teams = config('permission.teams');
// Nombre de tablas
$tableNames = config('permission.table_names');
// Nombre columnas
$columnNames = config('permission.column_names');
// FK de roles
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
// FK de permisos
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
// No permitir continuar sin tener declarado el nombre de las tablas
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
// No permitir continuar sin tener declarado el nombre de la columna de equipo
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
// Crear tabla de permisos
Schema::create($tableNames['permissions'], function (Blueprint $table) {
//$table->engine('InnoDB');
$table->bigIncrements('id'); // permission id
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->string('description')->nullable();
$table->foreignId('permission_type_id')
->nullable()
->constrained()
->nullOnDelete();
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
// Crear tabla de roles
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
//$table->engine('InnoDB');
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->string('description')->nullable();
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
// Crear tabla de relación de modelos con permisos
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
// Crear tabla de relación de modelos con roles
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
// Crear tabla de relación de roles con permisos
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop('permission_types');
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('reset_passwords', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('reset_passwords');
}
};

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('notifications', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('type');
$table->morphs('notifiable');
$table->text('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('notifications');
}
};

View File

@ -0,0 +1,15 @@
<?php namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DevSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call(RoleSeeder::class);
$this->call(UserSeeder::class);
}
}

View File

@ -0,0 +1,49 @@
<?php namespace Database\Seeders;
use App\Models\PermissionType;
use Illuminate\Database\Seeder;
use Notsoweb\LaravelCore\Traits\MySql\RolePermission;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class RoleSeeder extends Seeder
{
use RolePermission;
/**
* Run the database seeds.
*/
public function run(): void
{
$users = PermissionType::create([
'name' => 'Usuarios'
]);
[
$userIndex,
$userCreate,
$userEdit,
$userDestroy
] = $this->onCRUD('users', $users);
$userSettings = $this->onPermission('users.settings', 'Configuración de usuarios', $users);
// Desarrollador
Role::create([
'name' => 'developer',
'description' => 'Desarrollador'
])->givePermissionTo(Permission::all());
// Administrador
Role::create([
'name' => 'admin',
'description' => 'Administrador'
])->givePermissionTo(
$userIndex,
$userCreate,
$userEdit,
$userDestroy,
$userSettings
);
}
}

View File

@ -0,0 +1,32 @@
<?php namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Notsoweb\LaravelCore\Supports\UserSecureSupport;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$developer = UserSecureSupport::create('developer@notsoweb.com');
User::create([
'name' => 'Developer',
'paternal' => 'Notsoweb',
'email' => $developer->email,
'password' => $developer->hash,
])->assignRole(__('developer'));
$admin = UserSecureSupport::create('admin@notsoweb.com');
User::create([
'name' => 'Developer',
'paternal' => 'Notsoweb',
'email' => $admin->email,
'password' => $admin->hash,
])->assignRole(__('admin'));
}
}

20
lang/en/auth.php Normal file
View File

@ -0,0 +1,20 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'These credentials do not match our records.',
'password' => 'The provided password is incorrect.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

6
lang/en/cache.php Normal file
View File

@ -0,0 +1,6 @@
<?php
return [
'no-cache' => ':label ":var" does not exist in the cache',
'save' => ':label ":var" saved in the cache',
];

19
lang/en/pagination.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
];

23
lang/en/passwords.php Normal file
View File

@ -0,0 +1,23 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'no_match' => 'The provided password does not match your current password.',
'reset' => 'Your password has been reset!',
'sent' => 'We have emailed your password reset link!',
'throttled' => 'Please wait before retrying.',
'token' => 'This password reset token is invalid.',
'user' => "We can't find a user with that email address.",
];

174
lang/en/validation.php Normal file
View File

@ -0,0 +1,174 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'The :attribute must be accepted.',
'accepted_if' => 'The :attribute must be accepted when :other is :value.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute must only contain letters.',
'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
'alpha_num' => 'The :attribute must only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'array' => 'The :attribute must have between :min and :max items.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'numeric' => 'The :attribute must be between :min and :max.',
'string' => 'The :attribute must be between :min and :max characters.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'current_password' => 'The password is incorrect.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
'declined' => 'The :attribute must be declined.',
'declined_if' => 'The :attribute must be declined when :other is :value.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'doesnt_end_with' => 'The :attribute may not end with one of the following: :values.',
'doesnt_start_with' => 'The :attribute may not start with one of the following: :values.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values.',
'enum' => 'The selected :attribute is invalid.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'gt' => [
'array' => 'The :attribute must have more than :value items.',
'file' => 'The :attribute must be greater than :value kilobytes.',
'numeric' => 'The :attribute must be greater than :value.',
'string' => 'The :attribute must be greater than :value characters.',
],
'gte' => [
'array' => 'The :attribute must have :value items or more.',
'file' => 'The :attribute must be greater than or equal to :value kilobytes.',
'numeric' => 'The :attribute must be greater than or equal to :value.',
'string' => 'The :attribute must be greater than or equal to :value characters.',
],
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'array' => 'The :attribute must have less than :value items.',
'file' => 'The :attribute must be less than :value kilobytes.',
'numeric' => 'The :attribute must be less than :value.',
'string' => 'The :attribute must be less than :value characters.',
],
'lte' => [
'array' => 'The :attribute must not have more than :value items.',
'file' => 'The :attribute must be less than or equal to :value kilobytes.',
'numeric' => 'The :attribute must be less than or equal to :value.',
'string' => 'The :attribute must be less than or equal to :value characters.',
],
'mac_address' => 'The :attribute must be a valid MAC address.',
'max' => [
'array' => 'The :attribute must not have more than :max items.',
'file' => 'The :attribute must not be greater than :max kilobytes.',
'numeric' => 'The :attribute must not be greater than :max.',
'string' => 'The :attribute must not be greater than :max characters.',
],
'max_digits' => 'The :attribute must not have more than :max digits.',
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'array' => 'The :attribute must have at least :min items.',
'file' => 'The :attribute must be at least :min kilobytes.',
'numeric' => 'The :attribute must be at least :min.',
'string' => 'The :attribute must be at least :min characters.',
],
'min_digits' => 'The :attribute must have at least :min digits.',
'multiple_of' => 'The :attribute must be a multiple of :value.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'password' => [
'letters' => 'The :attribute must contain at least one letter.',
'mixed' => 'The :attribute must contain at least one uppercase and one lowercase letter.',
'numbers' => 'The :attribute must contain at least one number.',
'symbols' => 'The :attribute must contain at least one symbol.',
'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
],
'present' => 'The :attribute field must be present.',
'prohibited' => 'The :attribute field is prohibited.',
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
'prohibits' => 'The :attribute field prohibits :other from being present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_array_keys' => 'The :attribute field must contain entries for: :values.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_if_accepted' => 'The :attribute field is required when :other is accepted.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values are present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'array' => 'The :attribute must contain :size items.',
'file' => 'The :attribute must be :size kilobytes.',
'numeric' => 'The :attribute must be :size.',
'string' => 'The :attribute must be :size characters.',
],
'starts_with' => 'The :attribute must start with one of the following: :values.',
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid timezone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute must be a valid URL.',
'uuid' => 'The :attribute must be a valid UUID.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [],
];

4
lang/es.json Normal file
View File

@ -0,0 +1,4 @@
{
"Female": "Femenino",
"Male": "Masculino"
}

24
lang/es/auth.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'Estas credenciales no coinciden con nuestros registros.',
'password' => 'The provided password is incorrect.',
'throttle' => 'Demasiados intentos de acceso. Por favor inténtelo de nuevo en :seconds segundos.',
'forgot' => [
'subject' => 'Recuperación de contraseña',
'line' => 'Por favor, haga clic en el siguiente enlace para restaurar su contraseña. El enlace expira en 15 minutos.',
'button' => 'Restaurar contraseña',
],
];

6
lang/es/cache.php Normal file
View File

@ -0,0 +1,6 @@
<?php
return [
'no-cache' => ':label ":var" no existe en la cache',
'save' => ':label ":var" guardado en la cache',
];

5
lang/es/login.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
'required' => 'Es necesario autenticarse para continuar.'
];

19
lang/es/pagination.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Anterior',
'next' => 'Siguiente &raquo;',
];

24
lang/es/passwords.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'no_match' => 'La contraseña proporcionada no coincide con su contraseña actual.',
'reset' => '¡Su contraseña ha sido restablecida!',
'sent' => '¡Recordatorio de contraseña enviado!',
'token' => 'Este token de restablecimiento de contraseña es inválido.',
'user' => 'No se ha encontrado un usuario con esa dirección de correo.',
'throttled' => 'Por favor espere antes de volver a intentarlo.',
'password' => 'Las contraseñas deben tener al menos seis caracteres y coincidir con la confirmación.',
'verify' => 'Si el correo :email existe, te enviaremos un correo con el token de recuperación.'
];

174
lang/es/validation.php Normal file
View File

@ -0,0 +1,174 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'El campo :attribute debe ser aceptado.',
'active_url' => 'El campo :attribute no es una URL válida.',
'after' => 'El campo :attribute debe ser una fecha posterior a :date.',
'after_or_equal' => 'El campo :attribute debe ser una fecha posterior o igual a :date.',
'alpha' => 'El campo :attribute solo puede contener letras.',
'alpha_dash' => 'El campo :attribute solo puede contener letras, números, guiones y guiones bajos.',
'alpha_num' => 'El campo :attribute solo puede contener letras y números.',
'array' => 'El campo :attribute debe ser un array.',
'before' => 'El campo :attribute debe ser una fecha anterior a :date.',
'before_or_equal' => 'El campo :attribute debe ser una fecha anterior o igual a :date.',
'between' => [
'numeric' => 'El campo :attribute debe ser un valor entre :min y :max.',
'file' => 'El archivo :attribute debe pesar entre :min y :max kilobytes.',
'string' => 'El campo :attribute debe contener entre :min y :max caracteres.',
'array' => 'El campo :attribute debe contener entre :min y :max elementos.',
],
'boolean' => 'El campo :attribute debe ser verdadero o falso.',
'confirmed' => 'El campo confirmación de :attribute no coincide.',
'current_password' => 'La contraseñas son diferentes',
'date' => 'El campo :attribute no corresponde con una fecha válida.',
'date_equals' => 'El campo :attribute debe ser una fecha igual a :date.',
'date_format' => 'El campo :attribute no corresponde con el formato de fecha :format.',
'different' => 'Los campos :attribute y :other deben ser diferentes.',
'digits' => 'El campo :attribute debe ser un número de :digits dígitos.',
'digits_between' => 'El campo :attribute debe contener entre :min y :max dígitos.',
'dimensions' => 'El campo :attribute tiene dimensiones de imagen inválidas.',
'distinct' => 'El campo :attribute tiene un valor duplicado.',
'email' => 'El campo :attribute debe ser una dirección de correo válida.',
'ends_with' => 'El campo :attribute debe finalizar con alguno de los siguientes valores: :values',
'exists' => 'El campo :attribute seleccionado no existe.',
'exist_section' => 'Este :attribute no existe.',
'file' => 'El campo :attribute debe ser un archivo.',
'filled' => 'El campo :attribute debe tener un valor.',
'gt' => [
'numeric' => 'El campo :attribute debe ser mayor a :value.',
'file' => 'El archivo :attribute debe pesar más de :value kilobytes.',
'string' => 'El campo :attribute debe contener más de :value caracteres.',
'array' => 'El campo :attribute debe contener más de :value elementos.',
],
'gte' => [
'numeric' => 'El campo :attribute debe ser mayor o igual a :value.',
'file' => 'El archivo :attribute debe pesar :value o más kilobytes.',
'string' => 'El campo :attribute debe contener :value o más caracteres.',
'array' => 'El campo :attribute debe contener :value o más elementos.',
],
'image' => 'El campo :attribute debe ser una imagen.',
'in' => 'El campo :attribute es inválido.',
'in_array' => 'El campo :attribute no existe en :other.',
'integer' => 'El campo :attribute debe ser un número entero.',
'ip' => 'El campo :attribute debe ser una dirección IP válida.',
'ipv4' => 'El campo :attribute debe ser una dirección IPv4 válida.',
'ipv6' => 'El campo :attribute debe ser una dirección IPv6 válida.',
'json' => 'El campo :attribute debe ser una cadena de texto JSON válida.',
'lt' => [
'numeric' => 'El campo :attribute debe ser menor a :value.',
'file' => 'El archivo :attribute debe pesar menos de :value kilobytes.',
'string' => 'El campo :attribute debe contener menos de :value caracteres.',
'array' => 'El campo :attribute debe contener menos de :value elementos.',
],
'lte' => [
'numeric' => 'El campo :attribute debe ser menor o igual a :value.',
'file' => 'El archivo :attribute debe pesar :value o menos kilobytes.',
'string' => 'El campo :attribute debe contener :value o menos caracteres.',
'array' => 'El campo :attribute debe contener :value o menos elementos.',
],
'max' => [
'numeric' => 'El campo :attribute no debe ser mayor a :max.',
'file' => 'El archivo :attribute no debe pesar más de :max kilobytes.',
'string' => 'El campo :attribute no debe contener más de :max caracteres.',
'array' => 'El campo :attribute no debe contener más de :max elementos.',
],
'mimes' => 'El campo :attribute debe ser un archivo de tipo: :values.',
'mimetypes' => 'El campo :attribute debe ser un archivo de tipo: :values.',
'min' => [
'numeric' => 'El campo :attribute debe ser al menos :min.',
'file' => 'El archivo :attribute debe pesar al menos :min kilobytes.',
'string' => 'El campo :attribute debe contener al menos :min caracteres.',
'array' => 'El campo :attribute debe contener al menos :min elementos.',
],
'not_in' => 'El campo :attribute seleccionado es inválido.',
'not_regex' => 'El formato del campo :attribute es inválido.',
'numeric' => 'El campo :attribute debe ser un número.',
'password' => 'La contraseña es incorrecta.',
'present' => 'El campo :attribute debe estar presente.',
'regex' => 'El formato del campo :attribute es inválido.',
'required' => 'El campo :attribute es obligatorio.',
'required_if' => 'El campo :attribute es obligatorio cuando el campo :other es :value.',
'required_unless' => 'El campo :attribute es requerido a menos que :other se encuentre en :values.',
'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.',
'required_with_all' => 'El campo :attribute es obligatorio cuando :values están presentes.',
'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.',
'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de los campos :values están presentes.',
'same' => 'Los campos :attribute y :other deben coincidir.',
'size' => [
'numeric' => 'El campo :attribute debe ser :size.',
'file' => 'El archivo :attribute debe pesar :size kilobytes.',
'string' => 'El campo :attribute debe contener :size caracteres.',
'array' => 'El campo :attribute debe contener :size elementos.',
],
'starts_with' => 'El campo :attribute debe comenzar con uno de los siguientes valores: :values',
'string' => 'El campo :attribute debe ser una cadena de caracteres.',
'timezone' => 'El campo :attribute debe ser una zona horaria válida.',
'unique' => 'El valor del campo :attribute ya está en uso.',
'uploaded' => 'El campo :attribute no se pudo subir.',
'url' => 'El formato del campo :attribute es inválido.',
'uuid' => 'El campo :attribute debe ser un UUID válido.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => [
'barcode' => 'código de barras',
'section.number' => 'sección',
'name' => 'nombre',
'paternal' => 'apellido paterno',
'maternal' => 'apellido materno',
'email' => 'correo',
'phone' => 'teléfono',
'type_ck' => 'tipo',
'*.name' => 'nombre',
'*.paternal' => 'apellido paterno',
'*.maternal' => 'apellido materno',
'*.email' => 'correo',
'*.phone' => 'teléfono',
'*.street' => 'calle',
'*.inner_number' => 'número interior',
'*.outer_number' => 'número exterior',
'*.colony' => 'colonia',
'*.zipcode' => 'código postal',
'*.expiration' => 'vencimiento',
'*.section' => 'sección',
'*.voter_identifier' => 'clave de elector'
],
];

2948
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,10 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.7.4", "axios": "^1.7.4",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-echo": "^1.17.1",
"laravel-vite-plugin": "^1.0", "laravel-vite-plugin": "^1.0",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"pusher-js": "^8.4.0-rc2",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"vite": "^5.0" "vite": "^5.0"
} }

7
permission.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
read -p "Usuario del sistema: " myuser
chown -R $myuser:www-data bootstrap/cache/ storage/
chmod -R 775 bootstrap/cache/ storage/
echo "Done!"

1
public/profile Symbolic link
View File

@ -0,0 +1 @@
/var/www/notsoweb/holos.backend/storage/app/profile

View File

@ -2,3 +2,11 @@ import axios from 'axios';
window.axios = axios; window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allow your team to quickly build robust real-time web applications.
*/
import './echo';

14
resources/js/echo.js Normal file
View File

@ -0,0 +1,14 @@
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});

View File

@ -0,0 +1,12 @@
<x-mail::message>
# Introduction
The body of your message.
<x-mail::button :url="''">
Button Text
</x-mail::button>
Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

View File

@ -0,0 +1,10 @@
<x-mail::message>
{{ __('auth.forgot.line') }}
<x-mail::button :url="env('APP_FRONTEND_URL') . '/auth.html#/reset-password?code=12345234234'">
{{ __('auth.forgot.button') }}
</x-mail::button>
{{ __('thanks')}},<br>
{{ config('app.name') }}
</x-mail::message>

View File

@ -0,0 +1,24 @@
@props([
'url',
'color' => 'primary',
'align' => 'center',
])
<table class="action" align="{{ $align }}" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="{{ $align }}">
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="{{ $align }}">
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
<a href="{{ $url }}" class="button button-{{ $color }}" target="_blank" rel="noopener">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -0,0 +1,11 @@
<tr>
<td>
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>

View File

@ -0,0 +1,12 @@
@props(['url'])
<tr>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
@if (trim($slot) === 'Laravel')
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
@else
{{ $slot }}
@endif
</a>
</td>
</tr>

View File

@ -0,0 +1,58 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{ config('app.name') }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="color-scheme" content="light">
<meta name="supported-color-schemes" content="light">
<style>
@media only screen and (max-width: 600px) {
.inner-body {
width: 100% !important;
}
.footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
{{ $head ?? '' }}
</head>
<body>
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
{{ $header ?? '' }}
<!-- Email Body -->
<tr>
<td class="body" width="100%" cellpadding="0" cellspacing="0" style="border: hidden !important;">
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<!-- Body content -->
<tr>
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy ?? '' }}
</td>
</tr>
</table>
</td>
</tr>
{{ $footer ?? '' }}
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,27 @@
<x-mail::layout>
{{-- Header --}}
<x-slot:header>
<x-mail::header :url="config('app.url')">
{{ config('app.name') }}
</x-mail::header>
</x-slot:header>
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
<x-slot:subcopy>
<x-mail::subcopy>
{{ $subcopy }}
</x-mail::subcopy>
</x-slot:subcopy>
@endisset
{{-- Footer --}}
<x-slot:footer>
<x-mail::footer>
© {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }}
</x-mail::footer>
</x-slot:footer>
</x-mail::layout>

View File

@ -0,0 +1,14 @@
<table class="panel" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-content">
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-item">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -0,0 +1,7 @@
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>

View File

@ -0,0 +1,3 @@
<div class="table">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</div>

View File

@ -0,0 +1,291 @@
/* Base */
body,
body *:not(html):not(style):not(br):not(tr):not(code) {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
position: relative;
}
body {
-webkit-text-size-adjust: none;
background-color: #ffffff;
color: #718096;
height: 100%;
line-height: 1.4;
margin: 0;
padding: 0;
width: 100% !important;
}
p,
ul,
ol,
blockquote {
line-height: 1.4;
text-align: left;
}
a {
color: #3869d4;
}
a img {
border: none;
}
/* Typography */
h1 {
color: #3d4852;
font-size: 18px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h2 {
font-size: 16px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h3 {
font-size: 14px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
p {
font-size: 16px;
line-height: 1.5em;
margin-top: 0;
text-align: left;
}
p.sub {
font-size: 12px;
}
img {
max-width: 100%;
}
/* Layout */
.wrapper {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.content {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 0;
padding: 0;
width: 100%;
}
/* Header */
.header {
padding: 25px 0;
text-align: center;
}
.header a {
color: #3d4852;
font-size: 19px;
font-weight: bold;
text-decoration: none;
}
/* Logo */
.logo {
height: 75px;
max-height: 75px;
width: 75px;
}
/* Body */
.body {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
border-bottom: 1px solid #edf2f7;
border-top: 1px solid #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.inner-body {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
background-color: #ffffff;
border-color: #e8e5ef;
border-radius: 2px;
border-width: 1px;
box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);
margin: 0 auto;
padding: 0;
width: 570px;
}
/* Subcopy */
.subcopy {
border-top: 1px solid #e8e5ef;
margin-top: 25px;
padding-top: 25px;
}
.subcopy p {
font-size: 14px;
}
/* Footer */
.footer {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
margin: 0 auto;
padding: 0;
text-align: center;
width: 570px;
}
.footer p {
color: #b0adc5;
font-size: 12px;
text-align: center;
}
.footer a {
color: #b0adc5;
text-decoration: underline;
}
/* Tables */
.table table {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
width: 100%;
}
.table th {
border-bottom: 1px solid #edeff2;
margin: 0;
padding-bottom: 8px;
}
.table td {
color: #74787e;
font-size: 15px;
line-height: 18px;
margin: 0;
padding: 10px 0;
}
.content-cell {
max-width: 100vw;
padding: 32px;
}
/* Buttons */
.action {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
width: 100%;
float: unset;
}
.button {
-webkit-text-size-adjust: none;
border-radius: 4px;
color: #fff;
display: inline-block;
overflow: hidden;
text-decoration: none;
}
.button-blue,
.button-primary {
background-color: #2d3748;
border-bottom: 8px solid #2d3748;
border-left: 18px solid #2d3748;
border-right: 18px solid #2d3748;
border-top: 8px solid #2d3748;
}
.button-green,
.button-success {
background-color: #48bb78;
border-bottom: 8px solid #48bb78;
border-left: 18px solid #48bb78;
border-right: 18px solid #48bb78;
border-top: 8px solid #48bb78;
}
.button-red,
.button-error {
background-color: #e53e3e;
border-bottom: 8px solid #e53e3e;
border-left: 18px solid #e53e3e;
border-right: 18px solid #e53e3e;
border-top: 8px solid #e53e3e;
}
/* Panels */
.panel {
border-left: #2d3748 solid 4px;
margin: 21px 0;
}
.panel-content {
background-color: #edf2f7;
color: #718096;
padding: 16px;
}
.panel-content p {
color: #718096;
}
.panel-item {
padding: 0;
}
.panel-item p:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
}
/* Utilities */
.break-all {
word-break: break-all;
}

View File

@ -0,0 +1 @@
{{ $slot }}: {{ $url }}

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
{{ $slot }}: {{ $url }}

View File

@ -0,0 +1,9 @@
{!! strip_tags($header ?? '') !!}
{!! strip_tags($slot) !!}
@isset($subcopy)
{!! strip_tags($subcopy) !!}
@endisset
{!! strip_tags($footer ?? '') !!}

View File

@ -0,0 +1,27 @@
<x-mail::layout>
{{-- Header --}}
<x-slot:header>
<x-mail::header :url="config('app.url')">
{{ config('app.name') }}
</x-mail::header>
</x-slot:header>
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
<x-slot:subcopy>
<x-mail::subcopy>
{{ $subcopy }}
</x-mail::subcopy>
</x-slot:subcopy>
@endisset
{{-- Footer --}}
<x-slot:footer>
<x-mail::footer>
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
</x-mail::footer>
</x-slot:footer>
</x-mail::layout>

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1 @@
{{ $slot }}

View File

@ -0,0 +1,58 @@
<x-mail::message>
{{-- Greeting --}}
@if (! empty($greeting))
# {{ $greeting }}
@else
@if ($level === 'error')
# @lang('Whoops!')
@else
# @lang('Hello!')
@endif
@endif
{{-- Intro Lines --}}
@foreach ($introLines as $line)
{{ $line }}
@endforeach
{{-- Action Button --}}
@isset($actionText)
<?php
$color = match ($level) {
'success', 'error' => $level,
default => 'primary',
};
?>
<x-mail::button :url="$actionUrl" :color="$color">
{{ $actionText }}
</x-mail::button>
@endisset
{{-- Outro Lines --}}
@foreach ($outroLines as $line)
{{ $line }}
@endforeach
{{-- Salutation --}}
@if (! empty($salutation))
{{ $salutation }}
@else
@lang('Regards,')<br>
{{ config('app.name') }}
@endif
{{-- Subcopy --}}
@isset($actionText)
<x-slot:subcopy>
@lang(
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n".
'into your web browser:',
[
'actionText' => $actionText,
]
) <span class="break-all">[{{ $displayableActionUrl }}]({{ $actionUrl }})</span>
</x-slot:subcopy>
@endisset
</x-mail::message>

File diff suppressed because one or more lines are too long

64
routes/api.php Normal file
View File

@ -0,0 +1,64 @@
<?php
use App\Http\Controllers\MyUserController;
use App\Http\Controllers\System\LoginController;
use App\Http\Controllers\System\NotificationController;
use App\Http\Controllers\System\SystemController;
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Tighten\Ziggy\Ziggy;
Route::get('/', function () {
return ApiResponse::OK->response([
"message" => "It's fine :D"
]);
});
Route::name('api.')->group(function () {
Route::get('/routes', function () {
return (new Ziggy('api'))->toArray();
})->name('routes');
});
Route::middleware('auth:api')->group(function () {
// Aplicación
Route::prefix('user')->name('user.')->group(function() {
Route::get('/', [MyUserController::class, 'show'])->name('show');
Route::put('/', [MyUserController::class, 'update'])->name('update');
Route::delete('/', [MyUserController::class, 'destroy'])->name('destroy');
Route::delete('photo', [MyUserController::class, 'destroyPhoto'])->name('photo');
Route::put('password', [MyUserController::class, 'updatePassword'])->name('password');
Route::post('password-confirm', [MyUserController::class, 'confirmPassword'])->name('password-confirm');
Route::get('permissions', [MyUserController::class, 'permissions'])->name('permissions');
Route::get('roles', [MyUserController::class, 'roles'])->name('roles');
});
Route::prefix('users')->name('users.')->group(function() {
Route::prefix('{user}')->group(function() {
Route::get('roles', [UserController::class, 'roles'])->name('roles');
Route::put('roles', [UserController::class, 'updateRoles']);
Route::put('password', [UserController::class, 'updatePassword'])->name('password');
Route::get('permissions', [UserController::class, 'permissions'])->name('permissions');
});
});
Route::apiResource('users', UserController::class);
// Sistema
Route::prefix('system')->name('system.')->group(function() {
Route::get('permissions', [SystemController::class, 'permissions'])->name('permissions');
Route::get('roles', [SystemController::class, 'roles'])->name('roles');
Route::prefix('notifications')->name('notifications.')->group(function() {
Route::get('all-unread', [NotificationController::class, 'allUnread'])->name('all-unread');
Route::post('read', [NotificationController::class, 'read'])->name('read');
});
});
Route::post('auth/logout', [LoginController::class, 'logout'])->name('auth.logout');
});
Route::prefix('auth')->name('auth.')->group(function () {
Route::post('login', [LoginController::class, 'login'])->name('login');
Route::post('forgot-password', [LoginController::class, 'forgotPassword'])->name('forgot-password');
Route::post('reset-password', [LoginController::class, 'resetPassword'])->name('reset-password');
});

9
routes/channels.php Normal file
View File

@ -0,0 +1,9 @@
<?php use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Broadcast::channel('Global', function ($user) {
return $user->id !== null;
});

Some files were not shown because too many files have changed in this diff Show More