diff --git a/.styleci.yml b/.styleci.yml index 9daadf1..e7dd6bd 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -4,6 +4,6 @@ php: - no_unused_imports finder: not-name: - - index.php + - index.blade.php js: true css: true diff --git a/app/Http/Controllers/Managing/CinemaController.php b/app/Http/Controllers/Managing/CinemaController.php new file mode 100644 index 0000000..07e78d0 --- /dev/null +++ b/app/Http/Controllers/Managing/CinemaController.php @@ -0,0 +1,58 @@ +middleware('auth'); + $this->middleware('atleast:employee'); + } + + public function showAllCinemas() + { + return view('manage.cinemas.index', ['title' => "Manage Cinemas", 'cinemas' => \App\Models\Cinema::all()]); + } + + public function edit($id) + { + $c = \App\Models\Cinema::findOrfail($id); + return view('manage.cinemas.cinema', ['title' => "Manage Cinema", 'cinema' => $c, 'rooms' => $c->rooms]); + } + + public function createCinema() + { + return view('manage.cinemas.create', ['title' => "Create Cinema"]); + } + + public function store() + { + $cinema = new \App\Models\Cinema(); + $cinema->cinema_name = request('cinema_name'); + $cinema->address_id = request('address_id'); + $cinema->save(); + return redirect()->route('manage.cinemas'); + } + + public function show($id) + { + return view('main.cinemas.cinema', ['title' => "Edit Cinema", 'cinema' => \App\Models\Cinema::findOrfail($id)]); + } + + public function update($id) + { + $cinema = \App\Models\Cinema::findOrfail($id); + $cinema->cinema_name = request('cinema_name'); + $cinema->address_id = request('address_id'); + $cinema->save(); + return redirect()->route('manage.cinemas'); + } + + public function destroy($id) + { + $cinema = \App\Models\Cinema::findOrfail($id); + $cinema->delete(); + return redirect()->route('manage.cinemas'); + } +} diff --git a/app/Http/Controllers/Managing/GenreController.php b/app/Http/Controllers/Managing/GenreController.php new file mode 100644 index 0000000..8aa5079 --- /dev/null +++ b/app/Http/Controllers/Managing/GenreController.php @@ -0,0 +1,52 @@ +middleware('auth'); + $this->middleware('atleast:employee'); + } + + public function showAllGenres() { + return view('manage.genres.index', ['title' => "Manage Genres", 'genres' => \App\Models\Genre::all()]); + } + + public function edit($id) { + $g = \App\Models\Genre::findOrfail($id); + return view('manage.genres.genre', ['title' => "Manage Genre", 'genre' => $g, 'movies' => $g->movies]); + } + + public function createGenre() { + return view('manage.genres.create', ['title' => "Create Genre"]); + } + + public function store() { + $genre = new \App\Models\Genre(); + $genre->genre_name = request('genre_name'); + $genre->save(); + return redirect()->route('manage.genres'); + } + + public function show($id) { + return view('main.genres.genre', ['title' => "Edit Genre", 'genre' => \App\Models\Genre::findOrfail($id)]); + } + + public function update($id) { + $genre = \App\Models\Genre::findOrfail($id); + $genre->genre_name = request('genre_name'); + $genre->save(); + return redirect()->route('manage.genres'); + } + + public function destroy($id) { + $genre = \App\Models\Genre::findOrfail($id); + $genre->delete(); + return redirect()->route('manage.genres'); + } + +} diff --git a/app/Http/Controllers/Managing/MovieController.php b/app/Http/Controllers/Managing/MovieController.php new file mode 100644 index 0000000..9c9ea8c --- /dev/null +++ b/app/Http/Controllers/Managing/MovieController.php @@ -0,0 +1,67 @@ +middleware('auth'); + $this->middleware('atleast:employee'); + } + + public function showAllMovies() { + // has permission READ_MOVIES ? + if(!Auth::user()->allowedTo('READ_MOVIES')) { + abort(403); + } + return view('manage.movies.index', ['title' => "Manage Movies", 'movies' => \App\Models\Movie::all()]); + } + + public function edit($id) { + $m = \App\Models\Movie::findOrfail($id); + return view('manage.movies.movie', ['title' => "Manage Movie", 'movie' => $m, 'showings' => $m->showings]); + } + + public function createMovie() { + return view('manage.movies.create', ['title' => "Create Movie", 'genres' => \App\Models\Genre::all()]); + } + + public function store() { + $movie = new \App\Models\Movie(); + $movie->movie_name = request('movie_name'); + $movie->movie_description = request('movie_description'); + $movie->movie_year = request('movie_year'); + $movie->movie_image = request('movie_image'); +// $movie->user_id = auth()->user()->user_id; + $movie->genre_id = request('genre_id'); + $movie->save(); + return redirect()->route('manage.movies'); + } + + public function show($id) { + return view('main.movies.movie', ['title' => "Edit Movie", 'movie' => \App\Models\Movie::findOrfail($id)]); + } + + public function update($id) { + $movie = \App\Models\Movie::findOrfail($id); + $movie->movie_name = request('movie_name'); + $movie->movie_description = request('movie_description'); + $movie->movie_year = request('movie_year'); + $movie->movie_image = request('movie_image'); +// $movie->user_id = auth()->user()->user_id; + $movie->genre_id = request('genre_id') !== null ? request('genre_id') : $movie->genre_id; + $movie->save(); + return redirect()->route('manage.movies'); + } + + public function destroy($id) { + $movie = \App\Models\Movie::findOrfail($id); + $movie->delete(); + return redirect()->route('manage.movies'); + } + +} diff --git a/app/Http/Controllers/UserDashController.php b/app/Http/Controllers/UserDashController.php index 94b83cf..ff00f5b 100644 --- a/app/Http/Controllers/UserDashController.php +++ b/app/Http/Controllers/UserDashController.php @@ -23,6 +23,6 @@ public function __construct() */ public function index() { - return view('dash'); + return view('dash', ['title'=>"Dashboard", 'user'=>auth()->user()]); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 0079688..2da7d06 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -54,6 +54,7 @@ class Kernel extends HttpKernel */ protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, + 'atleast' => \App\Http\Middleware\AtleastRole::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, diff --git a/app/Http/Middleware/AtleastRole.php b/app/Http/Middleware/AtleastRole.php new file mode 100644 index 0000000..6ef28af --- /dev/null +++ b/app/Http/Middleware/AtleastRole.php @@ -0,0 +1,18 @@ +user()->atleast($role)) { + return redirect()->route('home'); + } + return $next($request); + } + +} diff --git a/app/Models/MovieGenre.php b/app/Models/Genre.php similarity index 56% rename from app/Models/MovieGenre.php rename to app/Models/Genre.php index caccda4..0e11588 100644 --- a/app/Models/MovieGenre.php +++ b/app/Models/Genre.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Model; -class MovieGenre extends Model +class Genre extends Model { protected $table = 'genres'; protected $primaryKey = 'genre_id'; @@ -13,9 +13,14 @@ class MovieGenre extends Model 'genre_name', ]; + public function findGenreByName($genre_name) + { + return Genre::where('genre_name', $genre_name)->first(); + } + public function movies() { - return $this->belongsToMany(Movie::class, 'movie_genres', 'genre_id', 'movie_id'); + return $this->hasMany('App\Models\Movie', 'genre_id'); } } diff --git a/app/Models/Movie.php b/app/Models/Movie.php index 0c5c998..8941b13 100644 --- a/app/Models/Movie.php +++ b/app/Models/Movie.php @@ -24,9 +24,14 @@ class Movie extends Model 'updated_at', ]; + public static function findOrfail($id) + { + return Movie::where('movie_id', $id)->firstOrFail(); + } + public function genre() { - return $this->belongsTo(MovieGenre::class, 'genre_id', 'genre_id'); + return $this->belongsTo(Genre::class, 'genre_id', 'genre_id'); } public function showings() diff --git a/app/Models/Permission.php b/app/Models/Permission.php index e564052..fb14ecc 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -17,11 +17,9 @@ class Permission extends Model 'updated_at', ]; - // Permissions are linked to users by the user_permissions table - // User permissions are linked to users by the user_id and are valid per date public function users() { - return $this->belongsToMany(User::class, 'user_permissions', 'permission_id', 'user_id'); + return $this->belongsToMany('App\Models\User', 'user_permissions', 'permission_id', 'user_id'); } } diff --git a/app/Models/Room.php b/app/Models/Room.php index f1b7b64..f9c5d79 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -15,6 +15,7 @@ class Room extends Model 'room_rows', 'room_columns', 'user_id', // who added the room? + 'cinema_id', ]; protected $hidden = [ @@ -22,10 +23,24 @@ class Room extends Model 'updated_at', ]; + public static function find(int $room_id) + { + return self::where('room_id', $room_id)->first(); + } + public function showings() { return $this->hasMany(Showing::class, 'room_id', 'room_id'); } + public function seats() + { + return $this->hasMany(Seat::class, 'room_id', 'room_id'); + } + + public function cinema() + { + return $this->belongsTo(Cinema::class, 'cinema_id', 'cinema_id'); + } } diff --git a/app/Models/Seat.php b/app/Models/Seat.php index 39891a7..09b2932 100644 --- a/app/Models/Seat.php +++ b/app/Models/Seat.php @@ -32,5 +32,23 @@ public function orders() return $this->belongsToMany(Order::class, 'order_seats', 'seat_id', 'order_id'); } + public function tickets() + { + return $this->hasMany(Ticket::class, 'seat_id', 'seat_id'); + } + + // isReserved(int showing_id) method + // Looks at showing / order / ticket if it's reserved + // Returns true if it is reserved, false if it isn't + public function is_reserved(int $showing_id) + { + $tickets = $this->tickets()->where('showing_id', $showing_id)->get(); + foreach ($tickets as $ticket) { + if ($ticket->order->order_status == 'pending' || $ticket->order->order_status == 'paid') { + return true; + } + } + return false; + } } diff --git a/app/Models/Showing.php b/app/Models/Showing.php index e7a0a67..45373c5 100644 --- a/app/Models/Showing.php +++ b/app/Models/Showing.php @@ -16,7 +16,6 @@ class Showing extends Model protected $fillable = [ 'movie_id', 'showing_start', - 'showing_end', 'room_id', // which room is showing the movie? 'user_id', // who added the showing? ]; @@ -26,6 +25,16 @@ class Showing extends Model 'updated_at', ]; + public static function findOrfail($id) + { + return Showing::where('showing_id', $id)->firstOrFail(); + } + + public function room() + { + return $this->belongsTo(Room::class, 'room_id', 'room_id'); + } + public function movie() { return $this->belongsTo(Movie::class, 'movie_id', 'movie_id'); diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 866c97f..5a56524 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -32,6 +32,4 @@ public function showing() return $this->belongsTo(Showing::class, 'showing_id', 'showing_id'); } - - } diff --git a/app/Models/User.php b/app/Models/User.php index ba0a14d..39e1b6a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,10 +14,6 @@ class User extends Authenticatable protected $primaryKey = 'user_id'; - // User can be a customer or an employee - // If customer, then there'll be a customer_id in the users table - // If employee, then there'll be an employee_id in the users table - /** * The attributes that are mass assignable. * @@ -26,10 +22,8 @@ class User extends Authenticatable protected $fillable = [ 'name', 'email', - 'password', - 'customer_id', // if this is filled, then this user is a customer - 'employee_id', // if this is filled, then this user is an employee - // if both are filled, then this user is both a customer and an employee + 'role', // default, employee, manage + 'password' ]; /** @@ -53,5 +47,43 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', ]; + public function permissions() + { + // get permissions for this user + // note: permissions are linked to users by the user_permissions table + // the permissions are in the permissions table + // only return valid permissions + return $this->belongsToMany('App\Models\Permission', 'user_permissions', 'user_id', 'permission_id')->where(function ($query) { + $query->where('user_permission_start', '<=', now())->where(function ($query) { + $query->where('user_permission_end', '>=', now())->orWhereNull('user_permission_end'); + }); + }); + } + + public function allowedTo($permission): bool + { + if($this->role === 'manage') { + return true; + } + if ($this->permissions()->where('permission_name', $permission)->first()) { + return true; + } + return false; + } + + public function atleast($role): bool + { + $hierarchy = [ + 'default' => 0, + 'employee' => 1, + 'manage' => 2 + ]; + return $hierarchy[$this->role] >= $hierarchy[$role]; + } + + public static function find($id) + { + return User::all()->where('user_id', $id)->first(); + } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index ea87f2e..f76e3bf 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -17,7 +17,7 @@ class RouteServiceProvider extends ServiceProvider * * @var string */ - public const HOME = '/home'; + public const HOME = '/dash'; /** * Define your route model bindings, pattern filters, and other route configuration. diff --git a/app/View/Components/SeatChooser.php b/app/View/Components/SeatChooser.php new file mode 100644 index 0000000..b3eaef9 --- /dev/null +++ b/app/View/Components/SeatChooser.php @@ -0,0 +1,51 @@ +room_id = $room_id; + $this->room = Room::find($room_id); + $this->showing_id = $showing_id; + } + + public function matrixGenerate() { + $matrix = []; + for ($row = 1; $row <= $this->room->room_rows; $row++) { + $matrix[$row] = []; + for ($column = 1; $column <= $this->room->room_columns; $column++) { + $matrix[$row][$column] = 0; + } + } + return $matrix; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\Contracts\View\View|\Closure|string + */ + public function render() + { + return view('components.seat-chooser', [ + 'room' => $this->room, + 'seatmatrix' => $this->matrixGenerate(), + 'showing_id' => $this->showing_id, + ]); + } +} diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 7bb929d..b5b9869 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -19,6 +19,7 @@ public function up() $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); + $table->enum('role', ['default', 'employee', 'manage'])->default('default'); $table->rememberToken(); $table->timestamps(); }); diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php deleted file mode 100644 index 425a4b9..0000000 --- a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php +++ /dev/null @@ -1,36 +0,0 @@ -id('failed_job_id'); - $table->string('uuid')->unique(); - $table->text('connection'); - $table->text('queue'); - $table->longText('payload'); - $table->longText('exception'); - $table->timestamp('failed_at')->useCurrent(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('failed_jobs'); - } -}; diff --git a/database/migrations/2022_11_22_000008_create_addresses_table.php b/database/migrations/2022_11_22_000000_create_addresses_table.php similarity index 88% rename from database/migrations/2022_11_22_000008_create_addresses_table.php rename to database/migrations/2022_11_22_000000_create_addresses_table.php index df8647c..3ec4299 100644 --- a/database/migrations/2022_11_22_000008_create_addresses_table.php +++ b/database/migrations/2022_11_22_000000_create_addresses_table.php @@ -19,8 +19,8 @@ public function up() $table->addColumn('string', 'address_city', ['length' => 255]); $table->addColumn('string', 'address_state', ['length' => 255]); $table->addColumn('string', 'address_zip', ['length' => 255]); - $table->addColumn('string', 'address_country', ['length' => 255]); - $table->foreignId('user_id')->constrained('users', 'user_id'); + $table->addColumn('string', 'address_country', ['length' => 255])->default('Netherlands'); + $table->addColumn('string', 'address_phone', ['length' => 255])->nullable(); }); } diff --git a/database/migrations/2022_11_22_000001_create_cinemas_table.php b/database/migrations/2022_11_22_000001_create_cinemas_table.php new file mode 100644 index 0000000..c224d89 --- /dev/null +++ b/database/migrations/2022_11_22_000001_create_cinemas_table.php @@ -0,0 +1,36 @@ +id('cinema_id'); + $table->timestamps(); + $table->string('cinema_name'); + $table->foreignId('address_id')->constrained('addresses', 'address_id'); + $table->foreignId('user_id')->constrained('users', 'user_id'); // who created this cinema + $table->timestamp('cinema_open')->nullable(); + $table->timestamp('cinema_close')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('cinemas'); + } +}; diff --git a/database/migrations/2022_11_22_000001_create_permissions_table.php b/database/migrations/2022_11_22_000002_create_permissions_table.php similarity index 88% rename from database/migrations/2022_11_22_000001_create_permissions_table.php rename to database/migrations/2022_11_22_000002_create_permissions_table.php index 5354fc5..4d354b6 100644 --- a/database/migrations/2022_11_22_000001_create_permissions_table.php +++ b/database/migrations/2022_11_22_000002_create_permissions_table.php @@ -17,6 +17,7 @@ public function up() $table->id('permission_id'); $table->timestamps(); $table->addColumn('string', 'permission_name', ['length' => 255]); + $table->addColumn('string', 'permission_description', ['length' => 511])->nullable(); $table->unique(['permission_name']); }); } diff --git a/database/migrations/2022_11_22_000002_create_genre_table.php b/database/migrations/2022_11_22_000003_create_genre_table.php similarity index 100% rename from database/migrations/2022_11_22_000002_create_genre_table.php rename to database/migrations/2022_11_22_000003_create_genre_table.php diff --git a/database/migrations/2022_11_22_000003_create_movies_table.php b/database/migrations/2022_11_22_000004_create_movies_table.php similarity index 100% rename from database/migrations/2022_11_22_000003_create_movies_table.php rename to database/migrations/2022_11_22_000004_create_movies_table.php diff --git a/database/migrations/2022_11_22_000004_create_rooms_table.php b/database/migrations/2022_11_22_000005_create_rooms_table.php similarity index 65% rename from database/migrations/2022_11_22_000004_create_rooms_table.php rename to database/migrations/2022_11_22_000005_create_rooms_table.php index 7a6c4ce..35016fc 100644 --- a/database/migrations/2022_11_22_000004_create_rooms_table.php +++ b/database/migrations/2022_11_22_000005_create_rooms_table.php @@ -16,10 +16,11 @@ public function up() Schema::create('rooms', function (Blueprint $table) { $table->id('room_id'); $table->timestamps(); - $table->addColumn('string', 'room_name', ['length' => 255]); - $table->addColumn('integer', 'room_rows'); - $table->addColumn('integer', 'room_columns'); - $table->unique(['room_name']); + $table->string('room_name'); + $table->integer('room_rows'); + $table->integer('room_columns'); + $table->foreignId('cinema_id')->constrained('cinemas', 'cinema_id'); + $table->foreignId('user_id')->constrained('users', 'user_id'); // who created this room }); } diff --git a/database/migrations/2022_11_22_000005_create_showings_table.php b/database/migrations/2022_11_22_000006_create_showings_table.php similarity index 90% rename from database/migrations/2022_11_22_000005_create_showings_table.php rename to database/migrations/2022_11_22_000006_create_showings_table.php index 9c796e7..bdf97e2 100644 --- a/database/migrations/2022_11_22_000005_create_showings_table.php +++ b/database/migrations/2022_11_22_000006_create_showings_table.php @@ -17,7 +17,7 @@ public function up() $table->id('showing_id'); $table->timestamps(); $table->dateTime('showing_start'); - $table->dateTime('showing_end'); + // no showing end time, that's implied by the movie length and 10 minutes between showings $table->foreignId('movie_id')->constrained('movies', 'movie_id'); $table->foreignId('room_id')->constrained('rooms', 'room_id'); $table->foreignId('user_id')->constrained('users', 'user_id'); diff --git a/database/migrations/2022_11_22_000006_create_prices_table.php b/database/migrations/2022_11_22_000007_create_prices_table.php similarity index 100% rename from database/migrations/2022_11_22_000006_create_prices_table.php rename to database/migrations/2022_11_22_000007_create_prices_table.php diff --git a/database/migrations/2022_11_22_000007_create_ratings_table.php b/database/migrations/2022_11_22_000008_create_ratings_table.php similarity index 100% rename from database/migrations/2022_11_22_000007_create_ratings_table.php rename to database/migrations/2022_11_22_000008_create_ratings_table.php diff --git a/database/migrations/2022_11_22_000009_create_user_permissions_table.php b/database/migrations/2022_11_22_000009_create_user_permissions_table.php index ebe87d5..33250ca 100644 --- a/database/migrations/2022_11_22_000009_create_user_permissions_table.php +++ b/database/migrations/2022_11_22_000009_create_user_permissions_table.php @@ -18,8 +18,9 @@ public function up() $table->timestamps(); $table->foreignId('permission_id')->constrained('permissions', 'permission_id'); $table->foreignId('user_id')->constrained('users', 'user_id'); - $table->date('user_permission_start'); - $table->date('user_permission_end'); + // end can be null to mark indefinitely + $table->date('user_permission_start')->useCurrent(); + $table->date('user_permission_end')->nullable(); }); } diff --git a/database/migrations/2022_11_22_000011_create_orders_table.php b/database/migrations/2022_11_22_000011_create_orders_table.php index a0d9348..993a14b 100644 --- a/database/migrations/2022_11_22_000011_create_orders_table.php +++ b/database/migrations/2022_11_22_000011_create_orders_table.php @@ -17,7 +17,7 @@ public function up() $table->id('order_id'); $table->timestamps(); $table->addColumn('integer', 'order_number'); - $table->addColumn('enum', 'order_status', ['values' => ['pending', 'paid', 'cancelled']]); + $table->enum('order_status', ['pending', 'paid', 'cancelled']); $table->foreignId('user_id')->constrained('users', 'user_id'); $table->foreignId('billing_address_id')->constrained('addresses', 'address_id'); $table->unique(['order_number', 'user_id']); diff --git a/database/seeders/CinemaSeeder.php b/database/seeders/CinemaSeeder.php new file mode 100644 index 0000000..1bf5cb4 --- /dev/null +++ b/database/seeders/CinemaSeeder.php @@ -0,0 +1,61 @@ + 'Cinema 1', + 'cinema_address' => [ + 'address_line_1' => '123 Main Street', + 'address_line_2' => 'Suite 1', + 'address_city' => 'City', + 'address_state' => 'State', + 'address_zip' => '12345', + 'address_phone' => '123-456-7890', + ], + ], + [ + 'cinema_name' => 'Cinema 2', + 'cinema_address' => [ + 'address_line_1' => '123 Main Street', + 'address_line_2' => 'Suite 2', + 'address_city' => 'City', + 'address_state' => 'State', + 'address_zip' => '12345', + 'address_phone' => '123-456-7890', + ], + ] + ]; + + + foreach ($cinemas as $cinema) { + $c = new \App\Models\Cinema(); + $c->cinema_name = $cinema['cinema_name']; + $c->user_id = 1; + + $a = new \App\Models\Address(); + $a->address_street = $cinema['cinema_address']['address_line_1']; + $a->address_city = $cinema['cinema_address']['address_city']; + $a->address_state = $cinema['cinema_address']['address_state']; + $a->address_zip = $cinema['cinema_address']['address_zip']; + $a->address_phone = $cinema['cinema_address']['address_phone']; + $a->save(); + + $c->address_id = $a->address_id; + $c->save(); + } + + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 76d96dc..37d3f8d 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -14,11 +14,14 @@ class DatabaseSeeder extends Seeder */ public function run() { - // \App\Models\User::factory(10)->create(); - - // \App\Models\User::factory()->create([ - // 'name' => 'Test User', - // 'email' => 'test@example.com', - // ]); + $this->call([ + PermissionSeeder::class, + UserSeeder::class, + GenreSeeder::class, + MovieSeeder::class, + CinemaSeeder::class, + RoomSeeder::class, + ShowingSeeder::class, + ]); } } diff --git a/database/seeders/GenreSeeder.php b/database/seeders/GenreSeeder.php new file mode 100644 index 0000000..abf0b1f --- /dev/null +++ b/database/seeders/GenreSeeder.php @@ -0,0 +1,78 @@ +insert([ + [ + 'genre_name' => 'Action', + ], + [ + 'genre_name' => 'Adventure', + ], + [ + 'genre_name' => 'Animation', + ], + [ + 'genre_name' => 'Comedy', + ], + [ + 'genre_name' => 'Crime', + ], + [ + 'genre_name' => 'Documentary', + ], + [ + 'genre_name' => 'Drama', + ], + [ + 'genre_name' => 'Family', + ], + [ + 'genre_name' => 'Fantasy', + ], + [ + 'genre_name' => 'History', + ], + [ + 'genre_name' => 'Horror', + ], + [ + 'genre_name' => 'Music', + ], + [ + 'genre_name' => 'Mystery', + ], + [ + 'genre_name' => 'Romance', + ], + [ + 'genre_name' => 'Science Fiction', + ], + [ + 'genre_name' => 'TV Movie', + ], + [ + 'genre_name' => 'Thriller', + ], + [ + 'genre_name' => 'War', + ], + [ + 'genre_name' => 'Western', + ], + ]); + } +} diff --git a/database/seeders/MovieSeeder.php b/database/seeders/MovieSeeder.php new file mode 100644 index 0000000..6c57f0d --- /dev/null +++ b/database/seeders/MovieSeeder.php @@ -0,0 +1,53 @@ + 'Alvin and the Chipmunks', + 'movie_description' => 'A struggling songwriter named Dave Seville finds success when he comes across a trio of singing chipmunks: mischievous leader Alvin, brainy Simon, and chubby, impressionable Theodore.', + 'movie_year' => '2007', + 'movie_age_limit' => 6, + 'movie_length' => 90, + 'movie_image' => 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/vRiB9uvcD0WYp7k2pAeWz9ukpuN.jpg', + 'genre_id' => (new \App\Models\Genre)->findGenreByName('Comedy')->genre_id, + ], + [ + 'movie_name' => 'Alvin and the Chipmunks: The Squeakquel', + 'movie_description' => 'Pop sensations Alvin, Simon and Theodore end up in the care of Dave Seville\'s twenty-something nephew Toby. The boys must put aside music super stardom to return to school, and are tasked with saving the school\'s music program by winning the $25,000 prize in a battle of the bands. But the Chipmunks unexpectedly meet their match in three singing chipmunks known as The Chipettes - Brittany, Eleanor and Jeanette. Romantic and musical sparks are ignited when the Chipmunks and Chipettes square off.', + 'movie_year' => '2009', + 'movie_age_limit' => 6, + 'movie_length' => 92, + 'movie_image' => 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/8mdPqOga5fty15nXmaNcK1fsNMa.jpg', + 'genre_id' => (new \App\Models\Genre)->findGenreByName('Comedy')->genre_id, + ], + ]; + + foreach ($movies as $movie) { + $m = new Movie(); + $m->movie_name = $movie['movie_name']; + $m->movie_description = $movie['movie_description']; + $m->movie_year = $movie['movie_year']; + $m->movie_age_limit = $movie['movie_age_limit']; + $m->movie_length = $movie['movie_length']; + $m->movie_image = $movie['movie_image']; + $m->genre_id = $movie['genre_id']; + $m->save(); + } + + } +} diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php new file mode 100644 index 0000000..86e5e41 --- /dev/null +++ b/database/seeders/PermissionSeeder.php @@ -0,0 +1,104 @@ +insert([ + [ + 'permission_name' => 'READ_GENRES', + 'permission_description' => 'Read Genres', + ], + [ + 'permission_name' => 'CREATE_GENRES', + 'permission_description' => 'Create Genres', + ], + [ + 'permission_name' => 'UPDATE_GENRES', + 'permission_description' => 'Update Genres', + ], + [ + 'permission_name' => 'DELETE_GENRES', + 'permission_description' => 'Delete Genres', + ], + + [ + 'permission_name' => 'READ_MOVIES', + 'permission_description' => 'Read Movies', + ], + [ + 'permission_name' => 'CREATE_MOVIES', + 'permission_description' => 'Create Movies', + ], + [ + 'permission_name' => 'UPDATE_MOVIES', + 'permission_description' => 'Update Movies', + ], + [ + 'permission_name' => 'DELETE_MOVIES', + 'permission_description' => 'Delete Movies', + ], + + [ + 'permission_name' => 'READ_SHOWINGS', + 'permission_description' => 'Read Showings', + ], + [ + 'permission_name' => 'CREATE_SHOWINGS', + 'permission_description' => 'Create Showings', + ], + [ + 'permission_name' => 'UPDATE_SHOWINGS', + 'permission_description' => 'Update Showings', + ], + [ + 'permission_name' => 'DELETE_SHOWINGS', + 'permission_description' => 'Delete Showings', + ], + + [ + 'permission_name' => 'READ_USERS', + 'permission_description' => 'Read Users', + ], + [ + 'permission_name' => 'CREATE_USERS', + 'permission_description' => 'Create Users', + ], + [ + 'permission_name' => 'UPDATE_USERS', + 'permission_description' => 'Update Users', + ], + [ + 'permission_name' => 'DELETE_USERS', + 'permission_description' => 'Delete Users', + ], + + [ + 'permission_name' => 'READ_PERMISSIONS', + 'permission_description' => 'Read Permissions', + ], + [ + 'permission_name' => 'CREATE_PERMISSIONS', + 'permission_description' => 'Create Permissions', + ], + [ + 'permission_name' => 'UPDATE_PERMISSIONS', + 'permission_description' => 'Update Permissions', + ], + [ + 'permission_name' => 'DELETE_PERMISSIONS', + 'permission_description' => 'Delete Permissions', + ], + ]); + } +} diff --git a/database/seeders/RoomSeeder.php b/database/seeders/RoomSeeder.php new file mode 100644 index 0000000..b3f329a --- /dev/null +++ b/database/seeders/RoomSeeder.php @@ -0,0 +1,57 @@ + 'Room 1', + 'room_rows' => 10, + 'room_columns' => 10, + ], + [ + 'room_name' => 'Room 2', + 'room_rows' => 10, + 'room_columns' => 10, + ], + [ + 'room_name' => 'Room 3', + 'room_rows' => 10, + 'room_columns' => 10, + ], + ]; + + foreach ($rooms as $room) { + $r = new \App\Models\Room(); + $r->room_name = $room['room_name']; + $r->room_rows = $room['room_rows']; + $r->room_columns = $room['room_columns']; + $r->user_id = 1; + $r->cinema_id = $cinema->cinema_id; + $r->save(); + + for ($row = 1; $row <= $r->room_rows; $row++) { + for ($column = 1; $column <= $r->room_columns; $column++) { + $s = new \App\Models\Seat(); + $s->seat_row = $row; + $s->seat_column = $column; + $s->room_id = $r->room_id; + $s->save(); + } + } + } + } + } +} diff --git a/database/seeders/ShowingSeeder.php b/database/seeders/ShowingSeeder.php new file mode 100644 index 0000000..255ce18 --- /dev/null +++ b/database/seeders/ShowingSeeder.php @@ -0,0 +1,50 @@ +; + + $showStart = new \DateTime(); + $showStart->add(new \DateInterval('P5D')); + + foreach (\App\Models\Room::all() as $room) { + $showings = [ + [ + 'showing_start' => $showStart->format('Y-m-d H:i:s'), + 'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'), + 'movie_id' => 1, + 'room_id' => $room->room_id, + ], + [ + 'showing_start' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'), + 'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'), + 'movie_id' => 2, + 'room_id' => $room->room_id, + ], + ]; + + foreach ($showings as $showing) { + $s = new \App\Models\Showing(); + $s->showing_start = $showing['showing_start']; + $s->movie_id = $showing['movie_id']; + $s->room_id = $showing['room_id']; + $s->user_id = 1; + $s->save(); + } + + } + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..93194e4 --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,26 @@ +name = 'system'; + $user->email = 'sys@local.host'; + $user->role = 'manage'; + $user->password = Hash::make('system'); + $user->save(); + } +} diff --git a/public/css/auth.css b/public/css/auth.css index e69de29..914d02c 100644 --- a/public/css/auth.css +++ b/public/css/auth.css @@ -0,0 +1,54 @@ +.form { + background: var(--second-bg); + padding: 1rem; + border-radius: 10px; +} + +.form .item { + margin-top: 1rem; + margin-right: 1rem; + margin-bottom: 0.2rem; +} + +.form .item input { + margin-top: 0.2rem; + width: 100%; + height: 2rem; + background: var(--third-bg); + color: var(--default-text); + border: none; + outline: none; + padding: 0.1rem 0.1rem 0.1rem 1rem; + border-radius: 5px; +} + +.form .item input:hover { + color: var(--default-text-invert); + filter: brightness(1.2); +} + +.form .item input:focus { + background: var(--second-bg); + filter: brightness(1.2); +} + +.form .item input:focus:hover { + color: var(--default-text); +} + +.form button { + color: var(--default-text); + background: var(--third-bg); + margin-top: 1rem; + width: 100%; + height: 2rem; + border-radius: 5px; + border: none; + outline: none; +} + +.form button:focus, +.form button:hover { + color: var(--default-text-invert); + filter: brightness(1.2); +} diff --git a/public/css/extra.css b/public/css/extra.css new file mode 100644 index 0000000..2055958 --- /dev/null +++ b/public/css/extra.css @@ -0,0 +1,32 @@ +.jumbotron { + position: relative; + margin: 0; + width: 100%; + height: 30vh; +} + +.jumbotron img.jumbotron-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + filter: blur(1px) brightness(0.5); +} + +.jumbotron .container { + position: relative; + z-index: 1; + display: grid; + place-items: center; + height: 100%; +} + +.jumbotron .container h1 { + font-size: 3rem; + font-weight: bold; + margin-bottom: 0; + color: var(--primary-color-text); +} + diff --git a/public/css/generic.css b/public/css/generic.css new file mode 100644 index 0000000..8f94506 --- /dev/null +++ b/public/css/generic.css @@ -0,0 +1,173 @@ +:root { + --default-text: #000; + --default-text-invert: #fff; + + --dim-text: #666; + + --primary-color: #f55; + --primary-color-text: #fff; + --secondary-color: #ed0; + --secondary-color-text: #000; + + --default-bg: #fff; + --second-bg: #ddd; + --third-bg: #bbb; + + --darkest-bg: #222; + + --inactive-item-bg: #eee; + --active-item-bg: #ccc; + + --highlight-bg: #f55; + --highlight-text: #fff; +} + +* { + transition: all .5s; +} + +@media (prefers-color-scheme: dark) { + :root { + --default-text: #fff; + --default-text-invert: #000; + + --dim-text: #999; + + --default-bg: #222; + --second-bg: #777; + --third-bg: #aaa; + + --darkest-bg: #111; + + --inactive-item-bg: #333; + --active-item-bg: #666; + } +} + +html { + color: var(--default-text); + background: var(--default-bg); + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +body { + margin: 0; + padding: 0; +} + +a { + color: var(--default-text); + text-decoration: none; +} + +a:hover { + color: var(--secondary-color); +} + + +.spread-h { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +ul { + list-style: none; + padding: 0; +} + +ul li { + margin: 0.5rem 0; + border-bottom: 1px solid var(--second-bg); +} + +ul.numbered { + counter-reset: li; + list-style: decimal; +} + +ul.numbered li { + counter-increment: li; +} + +.card { + background: var(--default-bg); + border: 1px solid var(--second-bg); + border-radius: 0.5rem; + padding: 1rem; + margin: 1rem 0; +} + +.clicky-card { + cursor: pointer; +} + +.clicky-card:hover .card { + background: var(--darkest-bg); +} + +button.button, +a.button { + display: grid; + place-items: center; + align-content: center; + font-weight: bold; + min-height: 2rem; + min-width: 4rem; + background: var(--primary-color); + color: var(--primary-color-text); + padding: 0.5rem 1rem; + border-radius: 0.5rem; + text-decoration: none; + outline: none; + border: none; + cursor: pointer; +} + +button.button:hover, +a.button:hover { + background: var(--secondary-color); + color: var(--secondary-color-text); +} + +table { + border-collapse: collapse; + width: 100%; +} + +table td { + border: 1px solid var(--second-bg); + padding: 0.5rem; +} + +table th { + border: 1px solid var(--second-bg); + padding: 0.5rem; + background: var(--second-bg); +} + +table tr:nth-child(even) { + background: var(--default-bg); + filter: brightness(0.7); +} + +table tr:nth-child(odd) { + background: var(--default-bg); +} + +table tr:hover { + background: var(--default-bg); + filter: brightness(1.2); +} + +tr { + border: 1px solid var(--second-bg); +} + +.hide { + display: none; +} + +.op0 { + opacity: 0; +} diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..2c66f8b --- /dev/null +++ b/public/css/main.css @@ -0,0 +1,77 @@ +header { + background: var(--second-bg); + display: grid; + grid-template-columns: 1.5fr 0.5fr; + height: 7vh; + align-items: center; +} + +header span { + font-size: 1.5rem; + font-weight: bold; + margin: 0.5rem 1rem; +} + +header div { + text-align: right; + display: inline; +} + +header div a { + padding: 1.5rem; +} + +header div a:hover { + font-size: 1.2rem; +} + +main { + margin: 0 auto; + padding: 1rem; + max-width: 800px; +} + + +/* #movies needs to be a grid with movie poster and show details on hover */ +#movies { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-gap: 1rem; +} + +#movies a { + position: relative; + overflow: hidden; + border-radius: 0.5rem; +} + +#movies a img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.5s; +} + +#movies a:hover img { + transform: scale(1.1); +} + +#movies a .details { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(0deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%); + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + opacity: 0; + transition: opacity 0.5s; +} + +#movies a:hover .details { + opacity: 1; +} + diff --git a/public/css/manage.css b/public/css/manage.css new file mode 100644 index 0000000..2700105 --- /dev/null +++ b/public/css/manage.css @@ -0,0 +1,170 @@ +header { + position: fixed; + top: 0; + left: 0; + background: var(--second-bg); + display: grid; + grid-template-columns: 1.5fr 0.5fr; + height: 7vh; + align-items: center; + width: 100%; + z-index: 1; +} + +header span { + font-size: 1.5rem; + font-weight: bold; + margin: 0.5rem 1rem; +} + +header div { + text-align: right; + display: inline; +} + +header div a { + padding: 1.5rem; +} + +/* side bar needs to be under header*/ +#sidebar { + background: var(--darkest-bg); + height: 93vh; + width: 20vw; + position: fixed; + top: 7vh; + left: 0; + z-index: 1; + overflow-x: hidden; + padding-top: 0.25rem; +} + +#sidebar a { + background: var(--inactive-item-bg); + padding: 0.5rem 1rem; + text-decoration: none; + color: var(--default-text); + display: block; + margin-top: 0.5rem; +} + +#sidebar a:hover { + filter: brightness(1.5); +} + +#sidebar a.active { + background: var(--highlight-bg); + color: var(--highlight-text); +} + +#sidebar a.child { + padding-left: 2rem; + margin-top: 0; +/* nice visual*/ + border-left: 1rem solid var(--second-bg); +} + +main { + background: var(--default-bg); + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 9vh 1vw 4vh 21vw; + padding: 0; +} + +.card { + display: grid; + background: var(--second-bg); + width: 10vw; + place-items: center; +} + +.card .big-stat { + font-size: 3rem; + font-weight: bold; +} + +#movies { + display: flex; + flex-direction: column; +} + +#movies a { + display: flex; + flex-direction: row; + margin: 0.5rem 0; + padding: 0.5rem; + background: var(--second-bg); + border-radius: 0.5rem; +} + +#movies a img { + width: 10vw; + height: 16vw; + object-fit: cover; + transition: transform 0.5s; +} + +#movies a:hover { + filter: brightness(1.5); +} + +#movies a .details { + display: flex; + flex-direction: column; + margin-left: 1rem; +} + + +/* form */ +.form { + display: flex; + flex-direction: column; + margin: 0.5rem 0; + padding: 0.5rem; + background: var(--second-bg); + border-radius: 0.5rem; +} + +.form .form-group { + display: flex; + flex-direction: column; + margin: 0.5rem 0; +} + +.form .form-group label { + margin: 0.5rem 0; +} + +form .form-group button, +form .form-group select, +form .form-group textarea, +form .form-group input { + padding: 0.5rem; + border-radius: 0.5rem; + background: var(--default-bg); + color: var(--default-text); + outline: none; + border: none; +} + +form .form-group button:hover, +form .form-group select:hover, +form .form-group textarea:hover, +form .form-group input:hover, +form .form-group button:focus, +form .form-group select:focus, +form .form-group textarea:focus, +form .form-group input:focus { + filter: brightness(1.5); +} + +form .form-group button { + background: var(--highlight-bg); + color: var(--highlight-text); + cursor: pointer; +} + +form .form-group button:hover { + filter: brightness(1.5); +} + diff --git a/public/css/movies.css b/public/css/movies.css new file mode 100644 index 0000000..232b0c8 --- /dev/null +++ b/public/css/movies.css @@ -0,0 +1,31 @@ +#movies { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + flex-direction: row; + gap: 1rem; +} + +/* Show .details on top of image */ +#movies a { + position: relative; +} + +#movies a .details { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: grid; + place-content: center; + align-content: center; + background: var(--second-bg); + opacity: 0; + color: var(--default-text); + transition: all .5s; +} + +#movies a:hover .details { + opacity: 0.7; +} diff --git a/public/css/seat-chooser.css b/public/css/seat-chooser.css new file mode 100644 index 0000000..e72fc48 --- /dev/null +++ b/public/css/seat-chooser.css @@ -0,0 +1,39 @@ +:root { + --spacing: 1rem; +} + +.seat-chooser { + padding: var(--spacing); + width: 100%; + height: 100%; + position: relative; + background: var(--second-bg); + border: 1px solid #ccc; + border-radius: 5px; + overflow: hidden; + + display: grid; + grid-template-columns: repeat(var(--cols), 1fr); + grid-template-rows: repeat(var(--rows), 1fr); + gap: var(--spacing); +} + +.seat-chooser .seat { + position: relative; + background: #5f5; + border: 1px solid #ccc; + border-radius: 5px; + overflow: hidden; + cursor: pointer; + width: 32px; + height: 32px; + transition: all 0.3s ease; +} + +.seat-chooser .seat.seat-reserved { + background: #f66; +} + +.seat-chooser .seat:hover { + background: var(--highlight-bg); +} diff --git a/public/css/style.css b/public/css/style.css deleted file mode 100644 index 80e48cd..0000000 --- a/public/css/style.css +++ /dev/null @@ -1,105 +0,0 @@ -:root { - --default-text: #000; - --primary-color: #bb00ff; - --secondary-color: #5eff00; - - --default-bg: #fff; - --second-bg: #ddd; - --third-bg: #bbb; -} - -* { - transition: all .5s; -} - -@media (prefers-color-scheme: dark) { - :root { - --default-text: #fff; - --default-bg: #222; - --second-bg: #777; - --third-bg: #aaa; - } -} - -html { - color: var(--default-text); - background: var(--default-bg); - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -body { - margin: 0; - padding: 0; -} - -a { - color: var(--default-text); - text-decoration: none; -} - -a:hover { - color: var(--primary-color); -} - -header { - background: var(--second-bg); - display: grid; - grid-template-columns: 1.5fr 0.5fr; - height: 7vh; - align-items: center; -} - -header span { - font-size: 1.5rem; - font-weight: bold; - margin: 0.5rem 1rem; -} - -header div { - text-align: right; - display: inline; -} - -header div a {padding: 1.5rem;} - -header div a:hover { - background: var(--third-bg); - padding: 1.5rem 1.5rem 2.5rem 1.5rem; -} - -main { - margin: 0 auto; - padding: 1rem; - max-width: 800px; -} - -#movies { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - flex-direction: row; - gap: 1rem; -} - -/* Show .details on top of image */ -#movies a { - position: relative; -} -#movies a .details { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: grid; - place-content: center; - align-content: center; - background: var(--second-bg); - opacity: 0; - color: var(--default-text); - transition: all .5s; -} - -#movies a:hover .details { - opacity: 0.7; -} diff --git a/public/js/jumbotron.js b/public/js/jumbotron.js new file mode 100644 index 0000000..42dc4bd --- /dev/null +++ b/public/js/jumbotron.js @@ -0,0 +1,27 @@ +let jumbotron = document.querySelector('.jumbotron'); +let topImageHolder = document.querySelector('.jumbotron-image[data-order="1"]'); +let bottomImageHolder = document.querySelector('.jumbotron-image[data-order="2"]'); +let images = jumbotron.getAttribute('data-images').split(','); +let delay = jumbotron.getAttribute('data-delay'); +let imageIndex = 0; + +// init +bottomImageHolder.src = images[imageIndex]; +imageIndex++; + +function loop() { + bottomImageHolder.classList.add('op0'); + bottomImageHolder.src = images[imageIndex]; + imageIndex++; + + topImageHolder.classList.add('op0'); + topImageHolder.src = images[imageIndex]; + imageIndex++; + + bottomImageHolder.classList.remove('op0'); + if (imageIndex >= images.length) { + imageIndex = 0; + } +} + +setInterval(loop, delay); diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 4de16bc..9e2c05f 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,17 +1,63 @@ -@extends('layout') +@extends('main.layout') -@section('head') +@push('head') -@endsection +@endpush @section('content')
Welcome to CineFlex, the best cinema management system in the world!
+Here you can see what movies are playing at your local cinema and buy tickets for them.
+ Cinemas +Genre Name | +Associated Movie Count | +
---|---|
+ + {{ $genre->genre_name }} + + | +Associated Movies: {{ $genre->movies->count() }} | +