updated permissions

This commit is contained in:
Edward Tirado Jr 2025-12-30 21:15:42 -06:00
parent 8f47f40b03
commit 83f7073b18
31 changed files with 1467 additions and 901 deletions

4
.gitignore vendored
View file

@ -21,3 +21,7 @@ yarn-error.log
/.nova
/.vscode
/.zed
/storage/debugbar
sail
docker

View file

@ -4,6 +4,7 @@ namespace App\Livewire;
use App\Models\Movie;
use Livewire\Component;
use function Laravel\Prompts\select;
class MovieDetailsPanel extends Component
{
@ -14,7 +15,19 @@ class MovieDetailsPanel extends Component
public function openPanel(int $movieId): void
{
$this->selectedMovie = Movie::find($movieId);
$this->selectedMovie = Movie::where('id', $movieId)
->select(
'id',
'title',
'plot',
'poster',
'director',
'year',
'actors',
'genre',
'mpaa_rating'
)
->first();
$this->showDetails = true;
}

View file

@ -33,7 +33,7 @@ class MovieList extends Component
public function getList()
{
$list = MovieListModel::with('movies')
$list = MovieListModel::with('movies:id,poster')
->find($this->id);
if ($list) {
@ -45,6 +45,12 @@ class MovieList extends Component
}
}
public function deleteList(): void
{
$this->list->delete();
$this->redirectRoute('lists');
}
public function filterMovies(): void
{
$this->filteredMovies = collect($this->list->movies)
@ -62,7 +68,7 @@ class MovieList extends Component
$this->getList();
}
public function updatedSettingsForm(): void
public function updatedSettingsFormIsPublic(): void
{
$this->settingsForm->save();
}

View file

@ -6,11 +6,33 @@ use App\Livewire\Forms\MovieListForm;
use App\Models\MovieList;
use Exception;
use Livewire\Component;
use function Laravel\Prompts\select;
class MovieLists extends Component
{
public MovieListForm $form;
public $lists = [];
public $lists;
public $sharedLists;
public function mount()
{
$this->lists = collect();
$this->sharedLists = collect();
$this->getLists();
}
public function getLists()
{
$user = auth()->user();
if ($user) {
$this->lists = MovieList::where('user_id', $user->id)
->select("name", "id", "is_public")
->get();
} else {
$this->redirectRoute('login');
}
}
public function addList(): void
{
@ -32,14 +54,8 @@ class MovieLists extends Component
$this->form->reset();
}
public function getLists()
{
$this->lists = MovieList::all();
}
public function render()
{
$this->getLists();
return view('livewire.lists');
}
}

View file

@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
@ -18,4 +19,9 @@ class MovieList extends Model
{
return $this->belongsToMany(Movie::class);
}
public function owner(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}

10
app/Models/Permission.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Permission extends Model
{
//
}

10
app/Models/Role.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
//
}

View file

@ -5,9 +5,12 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;
@ -44,6 +47,19 @@ class User extends Authenticatable
return $this->hasOne(UserProfile::class);
}
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
public function hasRole(string $role): bool
{
if (Context::hasHidden('roles')) {
return in_array(strtolower($role), Context::getHidden('roles'));
}
return $this->roles->contains('name', strtolower($role));
}
/**
* Get the user's initials
*/

View file

@ -0,0 +1,25 @@
<?php
namespace App\Policies;
use App\Models\MovieList;
use App\Models\User;
class MovieListPolicy
{
/**
* Create a new policy instance.
*/
public function __construct()
{
//
}
public function update(User $user, MovieList $movieList)
{
// If the user is the owner of the movie list or has been added as an editor for
// the movie list, allow them to update it.
return $movieList->owner()->id = $user->id; //|| $movieList->editors->contains($user->id);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Models\Interfaces\MovieDbInterface;
use App\Services\OmdbService;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
@ -15,6 +16,14 @@ class AppServiceProvider extends ServiceProvider
public function register(): void
{
$this->app->bind(MovieDbInterface::class, OmdbService::class);
Blade::directive('role', function ($role) {
return "<?php if(auth()->user()->hasRole({$role})) : ?>";
});
Blade::directive('endrole', function ($role) {
return "<?php endif; ?>";
});
}
/**

View file

@ -19,6 +19,7 @@
"symfony/mailgun-mailer": "^7.3"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.16",
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",

1925
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -147,11 +147,11 @@ return [
// Features::registration(),
Features::resetPasswords(),
Features::emailVerification(),
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
// 'window' => 0,
]),
// Features::twoFactorAuthentication([
// 'confirm' => true,
// 'confirmPassword' => true,
// // 'window' => 0,
// ]),
],
];

View file

@ -4,8 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
return new class extends Migration {
/**
* Run the migrations.
*/
@ -15,7 +14,7 @@ return new class extends Migration
$table->id();
$table->string('name');
$table->boolean('is_public')->default(false);
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete()->index();
$table->timestamps();
});
}

View file

@ -0,0 +1,36 @@
<?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('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('display_name');
$table->timestamps();
});
Schema::create('role_user', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('roles_user');
Schema::dropIfExists('roles');
}
};

View file

@ -0,0 +1,24 @@
<?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
{
//
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View file

@ -3,6 +3,7 @@
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
@ -16,7 +17,7 @@ class DatabaseSeeder extends Seeder
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'username' => 'testuser',
'email' => 'test@example.com',
]);
}

View file

@ -3,7 +3,7 @@
style="mask: radial-gradient(circle at left, transparent 10px, black 11px) top left / 51% 100% no-repeat,
radial-gradient(circle at right, transparent 10px, black 11px) top right / 51% 100% no-repeat;">
<div class="p-2 border-2 border-amber-200 m-2">
<h1 class="font-bold font-arial text-2xl text-amber-200">Movie Night</h1>
<a href="/"><h1 class="font-bold font-arial text-2xl text-amber-200">Movie Night</h1></a>
</div>
</div>

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Unauthorized'))
@section('code', '401')
@section('message', __('Unauthorized'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Payment Required'))
@section('code', '402')
@section('message', __('Payment Required'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Forbidden'))
@section('code', '403')
@section('message', __($exception->getMessage() ?: 'Forbidden'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Not Found'))
@section('code', '404')
@section('message', __('Not Found'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Page Expired'))
@section('code', '419')
@section('message', __('Page Expired'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Too Many Requests'))
@section('code', '429')
@section('message', __('Too Many Requests'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Server Error'))
@section('code', '500')
@section('message', __('Server Error'))

View file

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Service Unavailable'))
@section('code', '503')
@section('message', __('Service Unavailable'))

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title')</title>
<!-- Styles -->
<style>
html, body {
background-color: #fff;
color: #636b6f;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-weight: 100;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.content {
text-align: center;
}
.title {
font-size: 36px;
padding: 20px;
}
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="title">
@yield('message')
</div>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -24,17 +24,48 @@
</form>
</div>
<ul class="w-full flex flex-col gap-5">
@foreach($lists as $list)
<li class="flex py-5 justify-between text-center">
<a class="font-bold text-2xl" href="/lists/{{$list->id}}" wire:navigate>{{$list->name}}</a>
@if((bool)$list->is_public === true)
<i class="fa fa-earth" title="Public" aria-label="Public"></i>
@else
<i class="fa fa-lock" title="Private" aria-label="Private"></i>
@endif
</li>
@endforeach
</ul>
<div class="w-full flex flex-col gap-5">
<h2 class="text-2xl font-bold">Your Lists</h2>
@if(!$lists->isEmpty())
<ul class="w-full flex flex-col gap-2">
<hr/>
@foreach($lists as $list)
<li class="flex justify-between text-center">
<a class="text-xl" href="/lists/{{$list->id}}" wire:navigate>{{$list->name}}</a>
@if((bool)$list->is_public === true)
<i class="fa fa-earth my-auto" title="Public" aria-label="Public"></i>
@else
<i class="fa fa-lock my-auto" title="Private" aria-label="Private"></i>
@endif
</li>
<hr/>
@endforeach
</ul>
@else
<span>No lists found.</span>
@endif
</div>
<div class="w-full flex flex-col gap-5">
<h2 class="text-2xl font-bold">Shared With You</h2>
@if(!$sharedLists->isEmpty())
<ul>
<hr/>
@foreach($sharedLists as $list)
<li class="flex justify-between text-center">
<a class="text-xl" href="/lists/{{$list->id}}" wire:navigate>{{$list->name}}</a>
@if((bool)$list->is_public === true)
<i class="fa fa-earth my-auto" title="Public" aria-label="Public"></i>
@else
<i class="fa fa-lock my-auto" title="Private" aria-label="Private"></i>
@endif
</li>
<hr/>
@endforeach
</ul>
@else
<span>No lists found.</span>
@endif
</div>
</x-ui.card>
</div>

View file

@ -1,10 +1,13 @@
<div class=" text-white pt-10 flex flex-col gap-5">
<div class="flex flex-row justify-between items-center mx-2 sm:mx-0">
<h1 class="text-2xl sm:text-3xl font-bold">{{$list->name}}</h1>
<button type="button" wire:click="toggleSettings"
class="hover:bg-blue-600 cursor-pointer text-white px-4 py-2 rounded">
<i class="fas fa-cog text-2xl"></i>
</button>
@can('update', $list)
<button type="button" wire:click="toggleSettings"
class="hover:bg-blue-600 cursor-pointer text-white px-4 py-2 rounded">
<i class="fas fa-cog text-2xl"></i>
</button>
@endcan
</div>
<x-ui.card class="overflow-hidden min-h-screen">
@ -21,7 +24,8 @@
<div class="w-full flex-shrink-0 flex flex-col gap-5">
<div class="flex flex-col-reverse sm:flex-row gap-5 sm:gap-0 justify-between w-full">
<div>
<input class="flex bg-white p-2 rounded sm:w-100" type="text" placeholder="Filter movies"
<input class="flex bg-white p-2 rounded w-full sm:w-100" type="text"
placeholder="Filter movies"
wire:model.live="filterText"
wire:keyup="filterMovies"/>
</div>
@ -57,7 +61,7 @@
</div>
<div class="flex flex-col gap-2 w-full p-5">
<label for="list-name">List Name</label>
<label for="list-name" class="font-bold">List Name</label>
<div class="flex flex-row">
<input type="text" wire:model.live="settingsForm.name" id="list-name"
class="w-full p-2 rounded rounded-r-none bg-white"/>
@ -67,12 +71,34 @@
</div>
<div
class="flex items-center justify-between bg-gray-700 hover:bg-gray-500 hover:opacity-85 p-5 rounded">
<label for="is_public" class="text-white cursor-pointer">Make list public</label>
<label for="is_public" class="text-white font-bold cursor-pointer">Make list public</label>
<input type="checkbox"
id="is_public"
wire:model.live="settingsForm.isPublic"
class="w-5 h-5 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500">
</div>
<div class="p-5">
<span class="font-bold">Collaborators</span>
<ul>
<li>Bob</li>
<li>Eddie</li>
<li>Jane</li>
</ul>
</div>
<div
class="flex items-center justify-between bg-gray-700 hover:bg-gray-500 hover:opacity-85 p-5 rounded">
<label for="delete_list" class="text-white cursor-pointer">Delete List</label>
<button name="delete_list"
type="button"
class="bg-red-500 p-2 rounded font-bold"
wire:click="deleteList"
wire:confirm="Are you sure you want to delete this list?"
>
Delete List
</button>
</div>
</div>
</div>
</div>

View file

@ -1,5 +0,0 @@
<x-layouts.app>
<div class="text-white text-2xl">
<livewire:movie-lists/>
</div>
</x-layouts.app>