megacommit

This commit is contained in:
Didier Slof 2023-02-02 08:17:38 +01:00
parent 2451ab45cb
commit 34ed81516b
Signed by: didier
GPG key ID: 01E71F18AA4398E5
51 changed files with 1200 additions and 251 deletions

View file

@ -1,12 +0,0 @@
name: pull requests
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
jobs:
uneditable:
uses: laravel/.github/.github/workflows/pull-requests.yml@main

View file

@ -1,47 +0,0 @@
name: Tests
on:
push:
branches:
- master
- '*.x'
pull_request:
schedule:
- cron: '0 0 * * *'
permissions:
contents: read
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: ['8.0', 8.1, 8.2]
name: PHP ${{ matrix.php }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
coverage: none
- name: Install Composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Copy environment file
run: cp .env.example .env
- name: Generate app key
run: php artisan key:generate
- name: Execute tests
run: vendor/bin/phpunit

View file

@ -1,13 +0,0 @@
name: update changelog
on:
release:
types: [released]
permissions: {}
jobs:
update:
permissions:
contents: write
uses: laravel/.github/.github/workflows/update-changelog.yml@main

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Main;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class CinemaController extends Controller
{
public function showAllCinemas()
{
return view('main.cinemas.index', ['title' => "Cinemas", 'cinemas' => \App\Models\Cinema::all()]);
}
public function show($id)
{
return view('main.cinemas.cinema', ['title' => "Cinema", 'cinema' => \App\Models\Cinema::findOrfail($id)]);
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Main;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class GenreController extends Controller
{
public function show($id)
{
return view('main.genres.genre', ['title' => "Genre", 'genre' => \App\Models\Genre::findOrfail($id)]);
}
public function showAllGenres()
{
return view('main.genres.index', ['title' => "Genres", 'genres' => \App\Models\Genre::all()]);
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Main;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class MovieController extends Controller
{
public function showAllMovies()
{
return view('main.movies.index', ['title' => "Movies", 'movies' => \App\Models\Movie::all(), 'genres' => \App\Models\Genre::all(), 'showings' => \App\Models\Showing::all()]);
}
public function moviesNowShowing()
{
// map showings that are in the future to movies
$showings = \App\Models\Showing::all()->filter(function ($showing) {
return $showing->showing_start > now();
});
// $movies must be a collection of unique movies
$movies = collect();
foreach ($showings as $showing) {
if (!$movies->contains($showing->movie)) {
$movies->push($showing->movie);
}
}
return view('main.movies.index', ['title' => "Movies Now Showing", 'movies' => $movies, 'genres' => \App\Models\Genre::all(), 'showings' => \App\Models\Showing::all()]);
}
public function show($id)
{
return view('main.movies.movie', ['title' => "Movie", 'movie' => \App\Models\Movie::findOrfail($id)]);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Main;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ShowingController extends Controller
{
public function show($id)
{
return view('main.showings.showing', ['title' => "Showing", 'showing' => \App\Models\Showing::findOrfail($id)]);
}
public function showAllShowings()
{
return view('main.showings.index', ['title' => "Showings", 'showings' => \App\Models\Showing::all()]);
}
public function order($id)
{
return view('main.order', ['title' => "Order Tickets", 'showing' => \App\Models\Showing::findOrfail($id)]);
}
}

View file

@ -8,12 +8,15 @@ public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
$this->middleware('atleast:employee'); $this->middleware('atleast:employee');
$this->middleware('permission:manage_cinemas')->only(['create', 'store', 'edit', 'update', 'destroy']); $this->middleware('permission:READ_CINEMAS')->only('index', 'show');
$this->middleware('permission:CREATE_CINEMAS')->only('create', 'store');
$this->middleware('permission:UPDATE_CINEMAS')->only('edit', 'update');
$this->middleware('permission:DELETE_CINEMAS')->only('destroy');
} }
public function showAllCinemas() public function index()
{ {
return view('manage.cinemas.index', ['title' => "Manage Cinemas", 'cinemas' => \App\Models\Cinema::all()]); return view('manage.cinemas.index', ['title' => "Manage Cinemas", 'cinemas' => \App\Models\Cinema::all(), 'users' => \App\Models\User::all()]);
} }
public function edit($id) public function edit($id)

View file

@ -10,10 +10,13 @@ class GenreController extends Controller
public function __construct() { public function __construct() {
$this->middleware('auth'); $this->middleware('auth');
$this->middleware('atleast:employee'); $this->middleware('atleast:employee');
$this->middleware('permission:manage_genres')->only(['create', 'store', 'edit', 'update', 'destroy']); $this->middleware('permission:READ_GENRES')->only('index', 'show');
$this->middleware('permission:CREATE_GENRES')->only('create', 'store');
$this->middleware('permission:UPDATE_GENRES')->only('edit', 'update');
$this->middleware('permission:DELETE_GENRES')->only('destroy');
} }
public function showAllGenres() { public function index() {
return view('manage.genres.index', ['title' => "Manage Genres", 'genres' => \App\Models\Genre::all()]); return view('manage.genres.index', ['title' => "Manage Genres", 'genres' => \App\Models\Genre::all()]);
} }

View file

@ -11,14 +11,13 @@ class MovieController extends Controller
public function __construct() { public function __construct() {
$this->middleware('auth'); $this->middleware('auth');
$this->middleware('atleast:employee'); $this->middleware('atleast:employee');
$this->middleware('permission:manage_movies')->only(['create', 'store', 'edit', 'update', 'destroy']); $this->middleware('permission:READ_MOVIES')->only('index', 'show');
$this->middleware('permission:CREATE_MOVIES')->only('create', 'store');
$this->middleware('permission:UPDATE_MOVIES')->only('edit', 'update');
$this->middleware('permission:DELETE_MOVIES')->only('destroy');
} }
public function showAllMovies() { public function index() {
// 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()]); return view('manage.movies.index', ['title' => "Manage Movies", 'movies' => \App\Models\Movie::all()]);
} }
@ -37,6 +36,8 @@ public function store() {
$movie->movie_description = request('movie_description'); $movie->movie_description = request('movie_description');
$movie->movie_year = request('movie_year'); $movie->movie_year = request('movie_year');
$movie->movie_image = request('movie_image'); $movie->movie_image = request('movie_image');
$movie->movie_length = request('movie_length');
$movie->movie_age_limit = request('movie_age_limit');
// $movie->user_id = auth()->user()->user_id; // $movie->user_id = auth()->user()->user_id;
$movie->genre_id = request('genre_id'); $movie->genre_id = request('genre_id');
$movie->save(); $movie->save();

View file

@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Managing;
use App\Http\Controllers\Controller;
class ShowingsController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('atleast:employee');
$this->middleware('permission:READ_SHOWINGS')->only('index', 'show');
$this->middleware('permission:CREATE_SHOWINGS')->only('create', 'store');
$this->middleware('permission:UPDATE_SHOWINGS')->only('edit', 'update');
$this->middleware('permission:DELETE_SHOWINGS')->only('destroy');
}
public function index()
{
return view('manage.showings.index', ['title' => "Manage Showings", 'showings' => \App\Models\Showing::all()]);
}
public function edit($id)
{
$s = \App\Models\Showing::findOrfail($id);
return view('manage.showings.showing', ['title' => "Manage Showing", 'showing' => $s, 'movies' => \App\Models\Movie::all(), 'rooms' => \App\Models\Room::all(), 'cinemas' => \App\Models\Cinema::all()]);
}
public function createShowing()
{
return view('manage.showings.create', ['title' => "Create Showing", 'movies' => \App\Models\Movie::all(), 'rooms' => \App\Models\Room::all()]);
}
public function store()
{
$showing = new \App\Models\Showing();
$showing->movie_id = request('movie_id');
$showing->room_id = request('room_id');
$showing->start_time = request('start_time');
$showing->save();
return redirect()->route('manage.showings');
}
public function show($id)
{
return view('main.showings.showing', ['title' => "Edit Showing", 'showing' => \App\Models\Showing::findOrfail($id)]);
}
public function update($id)
{
$showing = \App\Models\Showing::findOrfail($id);
$showing->movie_id = request('movie_id');
$showing->room_id = request('room_id');
$showing->showing_start = request('showing_start');
$showing->save();
return redirect()->route('manage.showings');
}
public function destroy($id)
{
$showing = \App\Models\Showing::findOrfail($id);
$showing->delete();
return redirect()->route('manage.showings');
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers\Managing;
use App\Http\Controllers\Controller;
class UsersController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('atleast:manage');
$this->middleware('permission:READ_USERS')->only('index', 'show');
$this->middleware('permission:CREATE_USERS')->only('create', 'store');
$this->middleware('permission:UPDATE_USERS')->only('edit', 'update');
$this->middleware('permission:DELETE_USERS')->only('destroy');
}
public function index()
{
return view('manage.users.index', ['title' => "Manage Users", 'users' => \App\Models\User::all()]);
}
public function create()
{
return view('manage.users.create');
}
public function store()
{
$user = new \App\Models\User();
$user->name = request('name');
$user->email = request('email');
$user->password = request('password');
$user->role = request('role');
$user->save();
// user assignments (user_assignments.user_id, user_assignments.cinema_id)
$cinemas = request('cinemas');
if ($cinemas) {
foreach ($cinemas as $cinema) {
$user->cinemas()->attach($cinema);
}
}
return redirect()->route('manage.users');
}
public function show($id)
{
return view('manage.users.user', ['user' => \App\Models\User::findOrfail($id)]);
}
public function update($id)
{
$user = \App\Models\User::findOrfail($id);
$user->name = request('name');
$user->email = request('email');
$user->password = request('password');
$user->role = request('role');
$user->save();
// user assignments (user_assignments.user_id, user_assignments.cinema_id)
$cinemas = request('cinemas');
if ($cinemas) {
foreach ($cinemas as $cinema) {
$user->cinemas()->attach($cinema);
}
}
return redirect()->route('manage.users');
}
public function destroy($id)
{
$user = \App\Models\User::findOrfail($id);
$user->delete();
return redirect()->route('manage.users');
}
}

View file

@ -55,6 +55,7 @@ class Kernel extends HttpKernel
protected $routeMiddleware = [ protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class, 'auth' => \App\Http\Middleware\Authenticate::class,
'atleast' => \App\Http\Middleware\AtleastRole::class, 'atleast' => \App\Http\Middleware\AtleastRole::class,
'permission' => \App\Http\Middleware\Permission::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View file

@ -0,0 +1,33 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CinemaAccess
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, $cinema_id)
{
// does the user have access to the cinema (or is admin)
if (auth()->user()->atleast('admin')) {
return $next($request);
}
if (auth()->user()->cinemas->contains($cinema_id)) {
return $next($request);
}
return $next($request);
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Permission
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, $permission)
{
// if user.role is admin, allow
if ($request->user()->role == 'admin') {
return $next($request);
}
if (auth()->user()->hasPermission($permission)) {
return $next($request);
}
abort(403, "You need \"$permission\" permission");
}
}

53
app/Models/Cinema.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Cinema extends Model
{
use HasFactory;
protected $table = 'cinemas';
protected $primaryKey = 'cinema_id';
public $timestamps = false;
protected $fillable = [
'cinema_name',
'address_id',
'user_id', // who created this cinema
'cinema_open',
'cinema_close',
];
protected $hidden = [
'created_at',
'updated_at',
];
public function address()
{
return $this->belongsTo(Address::class, 'address_id', 'address_id');
}
public function rooms()
{
return $this->hasMany(Room::class, 'cinema_id', 'cinema_id');
}
public function showings()
{
return $this->hasManyThrough(Showing::class, Room::class, 'cinema_id', 'room_id', 'cinema_id', 'room_id');
}
public function find($id)
{
return $this->where('cinema_id', $id)->first();
}
public function users() {
//users associated
return $this->belongsToMany('App\Models\User', 'user_assignments', 'cinema_id', 'user_id');
}
}

View file

@ -22,4 +22,16 @@ public function users()
return $this->belongsToMany('App\Models\User', 'user_permissions', 'permission_id', 'user_id'); return $this->belongsToMany('App\Models\User', 'user_permissions', 'permission_id', 'user_id');
} }
public function find(mixed $permission_id)
{
return $this->where('permission_id', $permission_id)->first();
}
public function create(array $array)
{
$this->permission_name = $array['permission_name'];
$this->save();
return $this;
}
} }

View file

@ -41,12 +41,17 @@ public function movie()
return $this->belongsTo(Movie::class, 'movie_id', 'movie_id'); return $this->belongsTo(Movie::class, 'movie_id', 'movie_id');
} }
public function prices()
{
return $this->hasMany(Price::class, 'showing_id', 'showing_id');
}
public function nowPlaying() public function nowPlaying()
{ {
return $this->where('showing_start', '>=', now())->get(); return $this->where('showing_start', '>=', now())->get();
} }
public function end_time() { public function showing_end() {
$date = new Carbon($this->showing_start); $date = new Carbon($this->showing_start);
$date->addMinutes($this->movie->movie_length); $date->addMinutes($this->movie->movie_length);
return $date; return $date;

View file

@ -54,14 +54,17 @@ public function permissions()
// the permissions are in the permissions table // the permissions are in the permissions table
// only return valid permissions // only return valid permissions
return $this->belongsToMany('App\Models\Permission', 'user_permissions', 'user_id', 'permission_id')->where(function ($query) { 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_start', '<=', now())->where(function ($query) {
$query->where('user_permission_end', '>=', now())->orWhereNull('user_permission_end'); // $query->where('user_permission_end', '>=', now())->orWhereNull('user_permission_end');
}); // });
}); });
} }
public function allowedTo($permission): bool public function hasPermission($permission): bool
{ {
if ($this->role == 'admin') {
return true;
}
if ($this->permissions()->where('permission_name', $permission)->first()) { if ($this->permissions()->where('permission_name', $permission)->first()) {
return true; return true;
} }
@ -73,7 +76,8 @@ public function atleast($role): bool
$hierarchy = [ $hierarchy = [
'default' => 0, 'default' => 0,
'employee' => 1, 'employee' => 1,
'manage' => 2 'manage' => 2,
'admin' => 3
]; ];
return $hierarchy[$this->role] >= $hierarchy[$role]; return $hierarchy[$this->role] >= $hierarchy[$role];
} }
@ -83,4 +87,21 @@ public static function find($id)
return User::all()->where('user_id', $id)->first(); return User::all()->where('user_id', $id)->first();
} }
public function cinemas()
{
// check user_assignments table for all assignments for this user
if ($this->atleast('admin')) {
// if user is a manager or admin, return all cinemas
return Cinema::all();
} else {
// if user is an employee, return only the cinemas they are assigned to
return $this->belongsToMany('App\Models\Cinema', 'user_assignments', 'user_id', 'cinema_id')->get();
}
}
public function orders()
{
return $this->hasMany('App\Models\Order', 'user_id', 'user_id');
}
} }

View file

@ -19,7 +19,7 @@ public function up()
$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->enum('role', ['default', 'employee', 'manage'])->default('default'); $table->enum('role', ['default', 'employee', 'manage', 'admin'])->default('default');
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestamps();
}); });

View file

@ -19,8 +19,8 @@ public function up()
$table->foreignId('permission_id')->constrained('permissions', 'permission_id'); $table->foreignId('permission_id')->constrained('permissions', 'permission_id');
$table->foreignId('user_id')->constrained('users', 'user_id'); $table->foreignId('user_id')->constrained('users', 'user_id');
// end can be null to mark indefinitely // end can be null to mark indefinitely
$table->date('user_permission_start')->useCurrent(); // $table->date('user_permission_start')->useCurrent();
$table->date('user_permission_end')->nullable(); // $table->date('user_permission_end')->nullable();
}); });
} }

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_assignments', function (Blueprint $table) {
$table->id('user_assignment_id');
$table->timestamps();
$table->foreignId('cinema_id')->constrained('cinemas', 'cinema_id');
$table->foreignId('user_id')->constrained('users', 'user_id');
$table->unique(['cinema_id', 'user_id']); // only one user per cinema
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_assignments');
}
};

View file

@ -23,17 +23,14 @@ public function run()
], ],
[ [
'room_name' => 'Room 2', 'room_name' => 'Room 2',
'room_rows' => 10, 'room_rows' => 5,
'room_columns' => 10, 'room_columns' => 5,
],
[
'room_name' => 'Room 3',
'room_rows' => 10,
'room_columns' => 10,
], ],
]; ];
foreach ($rooms as $room) { foreach ($rooms as $room) {
$this->command->info("Creating room {$room['room_name']} for cinema {$cinema->cinema_name}");
$r = new \App\Models\Room(); $r = new \App\Models\Room();
$r->room_name = $room['room_name']; $r->room_name = $room['room_name'];
$r->room_rows = $room['room_rows']; $r->room_rows = $room['room_rows'];
@ -43,6 +40,7 @@ public function run()
$r->save(); $r->save();
for ($row = 1; $row <= $r->room_rows; $row++) { for ($row = 1; $row <= $r->room_rows; $row++) {
$this->command->info("Creating row {$row} for room {$r->room_name}");
for ($column = 1; $column <= $r->room_columns; $column++) { for ($column = 1; $column <= $r->room_columns; $column++) {
$s = new \App\Models\Seat(); $s = new \App\Models\Seat();
$s->seat_row = $row; $s->seat_row = $row;

View file

@ -27,22 +27,60 @@ public function run()
'showing_end' => $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' => 1, 'movie_id' => 1,
'room_id' => $room->room_id, 'room_id' => $room->room_id,
'pricings' => [
[
'price' => 10,
'price_type' => 'adult',
],
[
'price' => 8,
'price_type' => 'senior',
],
[
'price' => 2.5,
'price_type' => 'child',
],
],
], ],
[ [
'showing_start' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'), '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'), 'showing_end' => $showStart->add(new \DateInterval('PT2H'))->format('Y-m-d H:i:s'),
'movie_id' => 2, 'movie_id' => 2,
'room_id' => $room->room_id, 'room_id' => $room->room_id,
'pricings' => [
[
'price' => 10,
'price_type' => 'adult',
], ],
[
'price' => 8,
'price_type' => 'senior',
],
[
'price' => 3.5,
'price_type' => 'child',
],
],
]
]; ];
foreach ($showings as $showing) { foreach ($showings as $showing) {
$this->command->info("Creating showing for movie {$showing['movie_id']} in room {$room->room_name}");
$s = new \App\Models\Showing(); $s = new \App\Models\Showing();
$s->showing_start = $showing['showing_start']; $s->showing_start = $showing['showing_start'];
$s->movie_id = $showing['movie_id']; $s->movie_id = $showing['movie_id'];
$s->room_id = $showing['room_id']; $s->room_id = $showing['room_id'];
$s->user_id = 1; $s->user_id = 1;
$s->save(); $s->save();
foreach ($showing['pricings'] as $pricing) {
$p = new \App\Models\Price();
$p->price = $pricing['price'];
$p->price_type = $pricing['price_type'];
$p->showing_id = $s->showing_id;
$p->user_id = 1;
$p->save();
}
} }
} }

View file

@ -3,6 +3,17 @@ version: '3.3'
# LARAVEL ENVIRONMENT COMPOSE # LARAVEL ENVIRONMENT COMPOSE
services: services:
mysql:
image: mysql:5.7
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: laravel
ports:
- "3306:3306"
phpmyadmin: phpmyadmin:
image: phpmyadmin/phpmyadmin image: phpmyadmin/phpmyadmin
container_name: phpmyadmin container_name: phpmyadmin
@ -10,9 +21,8 @@ services:
ports: ports:
- "8080:80" - "8080:80"
environment: environment:
PMA_HOST: neo.faulty.nl PMA_HOST: mysql
PMA_PORT: 33061 PMA_USER: root
PMA_USER: laravel PMA_PASSWORD: root
PMA_PASSWORD: laravel
PMA_ARBITRARY: 1 PMA_ARBITRARY: 1
PMA_ABSOLUTE_URI: http://localhost:8080/ PMA_ABSOLUTE_URI: http://localhost:8080/

View file

@ -130,6 +130,19 @@ a.button:hover {
color: var(--secondary-color-text); color: var(--secondary-color-text);
} }
button.button:active,
a.button:active {
background: var(--primary-color);
color: var(--primary-color-text);
}
button.button:disabled,
a.button:disabled {
background: var(--inactive-item-bg);
color: var(--dim-text);
cursor: not-allowed;
}
table { table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
@ -175,3 +188,14 @@ .op0 {
.mono { .mono {
font-family: monospace; font-family: monospace;
} }
input.text {
/* generic input adjustments */
background: var(--second-bg);
color: var(--default-text);
border: none;
outline: none;
padding: 0.5rem;
border-radius: 0.5rem;
font-size: 1rem;
}

View file

@ -68,9 +68,16 @@ #movies a .details {
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
opacity: 0; opacity: 0;
padding: 1rem;
max-width: calc(100% - 2rem);
transition: opacity 0.5s; transition: opacity 0.5s;
} }
#movies a .details span {
overflow: hidden;
text-overflow: ellipsis;
}
#movies a:hover .details { #movies a:hover .details {
opacity: 1; opacity: 1;
} }

View file

@ -46,14 +46,18 @@ #sidebar a {
color: var(--default-text); color: var(--default-text);
display: block; display: block;
margin-top: 0.5rem; margin-top: 0.5rem;
transition: all 0.5s;
} }
#sidebar a:hover { #sidebar a:hover {
filter: brightness(1.5); background: var(--second-bg);
color: var(--default-text);
} }
#sidebar a.active { #sidebar a.active {
background: var(--highlight-bg); border-left: 0.5rem solid var(--highlight-bg);
background-color: var(--default-bg);
color: var(--highlight-text); color: var(--highlight-text);
} }
@ -61,7 +65,11 @@ #sidebar a.child {
padding-left: 2rem; padding-left: 2rem;
margin-top: 0; margin-top: 0;
/* nice visual*/ /* nice visual*/
border-left: 1rem solid var(--second-bg); border-left: 0.5rem solid var(--second-bg);
}
#sidebar a.child.active {
border-left: 1rem solid var(--highlight-bg);
} }
main { main {
@ -135,10 +143,10 @@ .form .form-group label {
margin: 0.5rem 0; margin: 0.5rem 0;
} }
form .form-group button, .form .form-group button,
form .form-group select, .form .form-group select,
form .form-group textarea, .form .form-group textarea,
form .form-group input { .form .form-group input {
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.5rem; border-radius: 0.5rem;
background: var(--default-bg); background: var(--default-bg);
@ -147,14 +155,14 @@ form .form-group input {
border: none; border: none;
} }
form .form-group button:hover, .form .form-group button:hover,
form .form-group select:hover, .form .form-group select:hover,
form .form-group textarea:hover, .form .form-group textarea:hover,
form .form-group input:hover, .form .form-group input:hover,
form .form-group button:focus, .form .form-group button:focus,
form .form-group select:focus, .form .form-group select:focus,
form .form-group textarea:focus, .form .form-group textarea:focus,
form .form-group input:focus { .form .form-group input:focus {
filter: brightness(1.5); filter: brightness(1.5);
} }
@ -189,3 +197,15 @@ #showings .showing .actions {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.warning {
background-color: rgba(255, 0, 0, 0.5);
padding: 1rem;
border-radius: 0.5rem;
margin: 1rem 0;
min-width: 25vw;
}
.warning h3 {
margin: 0;
}

53
public/css/order.css Normal file
View file

@ -0,0 +1,53 @@
body {
/* center with spacing on the left and right*/
margin: 0 auto;
width: 100%;
max-width: 1000px;
padding: 0 20px 3rem;
}
#seat-chooser {
border: 1px solid black;
padding: 2rem;
}
#summary {
background-color: var(--second-bg);
border-radius: 5px;
padding: 1rem;
margin: 1rem 0;
}
#summary-dyn .seat {
background-color: var(--darkest-bg);
color: var(--primary-color-text);
border-radius: 5px;
padding: 0.5rem;
margin: 0.5rem;
}
#summary-dyn .seat select.ticket-select {
background-color: var(--primary-color);
color: var(--primary-color-text);
border: 0;
border-radius: 5px;
padding: 0.5rem;
margin: 0.5rem;
}
#summary-dyn .seat .seat-span {
background-color: var(--primary-color);
color: var(--primary-color-text);
border-radius: 5px;
padding: 0.5rem;
margin: 0.5rem;
}
#summary-dyn .seat .price {
background-color: var(--secondary-color);
color: var(--darkest-bg);
border-radius: 5px;
padding: 0.5rem;
margin: 0.5rem;
float: right;
}

View file

@ -12,8 +12,6 @@ #seat-chooser {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%;
height: 100%;
--seat-width: 2.5rem; --seat-width: 2.5rem;
--seat-height: var(--seat-width); --seat-height: var(--seat-width);
@ -95,24 +93,7 @@ #seat-chooser .row .seat:hover {
filter: brightness(0.8); filter: brightness(0.8);
} }
#load-screen { #seat-chooser #seat-chooser-status {
position: fixed; text-align: center;
top: 0; margin: 1rem;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
div.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
} }

24
public/js/freq.js Normal file
View file

@ -0,0 +1,24 @@
class FreqManager {
#store;
static getInstance() {
if (this._instance === null) {
this._instance = new FreqManager();
}
return this._instance;
}
constructor() {
this.#store = {};
}
set(key, value) {
this.#store[key] = value;
}
get(key) {
return this.#store[key];
}
}
window.freq = FreqManager.getInstance();

106
public/js/order.js Normal file
View file

@ -0,0 +1,106 @@
// DEPENDS ON seat-chooser.js
(async () => {
let scDiv = document.querySelector('#seat-chooser');
let summaryDiv = document.querySelector('#summary-dyn');
let showingId = scDiv.getAttribute('data-showing-id');
let ticketTypes = await (await fetch(`/api/showing/${showingId}/prices`)).json();
let sc = new SeatChooser(
showingId,
scDiv,
(sc, s, add) => {
// seat click callback
// sc is the seat chooser
// s is the seat // but it's better to look at sc.selectedSeats
// add is a boolean indicating whether the seat is being added or removed
// update the summary
summaryDiv.innerHTML = '';
sc.selectedSeats.forEach((seat, index) => {
// set seat ticket if ticket is not set
if (!seat.ticket) seat.ticket = ticketTypes[0];
let seatDiv = document.createElement('div');
seatDiv.classList.add('seat');
let ticketSelect = document.createElement('select');
ticketTypes.forEach((ticketType, index) => {
let option = document.createElement('option');
option.value = index;
option.innerText = ticketType.price_type;
ticketSelect.appendChild(option);
});
ticketSelect.classList.add('ticket-select');
ticketSelect.value = ticketTypes.indexOf(seat.ticket);
ticketSelect.addEventListener('change', (e) => {
seat.ticket = ticketTypes[e.target.value];
updateTicket(sc, seat, seatDiv);
updateTotal(sc);
});
seatDiv.appendChild(ticketSelect);
seatDiv.appendChild(document.createTextNode(' '));
let seatSpan = document.createElement('span');
seatSpan.innerText = `Row ${seat.row}, Seat ${seat.col}`;
seatSpan.classList.add('seat-span');
seatDiv.appendChild(seatSpan);
seatDiv.appendChild(document.createTextNode(' '));
let priceSpan = document.createElement('span');
priceSpan.classList.add('price');
seatDiv.appendChild(priceSpan);
summaryDiv.appendChild(seatDiv);
updateTicket(sc, seat, seatDiv);
});
updateTotal(sc);
if (sc.selectedSeats.length == 0) document.querySelector('button#order-button').disabled = true;
else document.querySelector('button#order-button').disabled = false;
}
);
window.freq.set('seat-chooser', sc);
})();
let total = 0;
function updateTicket(sc, seat, seatDiv) {
seatDiv.querySelector('span:last-child').innerText = `${seat.ticket.price}`;
}
function updateTotal(sc) {
total = 0;
sc.selectedSeats.forEach(_seat => {
total += Number(_seat.ticket.price);
});
document.querySelector('#summary-total').innerText = `Total: €${total}`;
}
document.querySelector('button#order-button').addEventListener('click', (e) => {
let seats = [];
window.sc.selectedSeats.forEach(seat => {
seats.push({
seat: seat.id,
ticket: seat.ticket.id
});
});
fetch(`/api/order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
showing: sc.showingId,
seats: seats
})
}).then(res => {
if (res.ok) {
window.location = '/order/complete';
} else {
alert('Error placing order');
}
});
});

View file

@ -26,19 +26,21 @@ class SeatChooser {
status.style.textAlign = 'center'; status.style.textAlign = 'center';
this.container.appendChild(status); this.container.appendChild(status);
} }
// set the message // set the message if it exists
if (this.container.querySelector('#seat-chooser-status-message'))
this.container.querySelector('#seat-chooser-status-message').innerText = msg; this.container.querySelector('#seat-chooser-status-message').innerText = msg;
} }
constructor(showingId, container, submitCallback) { constructor(showingId, container, seatClickCallback = (sc, s) => {}, submitCallback = null) {
this.showingId = showingId; // id of the showing this.showingId = showingId; // id of the showing
this.container = container; // the container to render the seat chooser in this.container = container; // the container to render the seat chooser in
this.submitCallback = submitCallback; // callback function to call when the user submits the form
this.setLoadingStatus("Initializing..."); this.setLoadingStatus("Initializing...");
this.seats = []; // array of nulls and Seat objects this.submitCallback = submitCallback; // callback function to call when the user submits the form
this.seatClickCallback = seatClickCallback; // callback function to call when the user clicks on a seat
this.seats = []; // array of nulls and Seat objects
this.selectedSeats = []; // array of Seat objects this.selectedSeats = []; // array of Seat objects
this.init(); this.init();
@ -171,6 +173,7 @@ class SeatChooser {
}); });
buttonContainer.appendChild(clearButton); buttonContainer.appendChild(clearButton);
if (this.submitCallback !== null) {
// submit button // submit button
let submitButton = document.createElement('button'); let submitButton = document.createElement('button');
submitButton.classList.add('button'); submitButton.classList.add('button');
@ -180,6 +183,7 @@ class SeatChooser {
this.submitCallback(this); this.submitCallback(this);
}); });
buttonContainer.appendChild(submitButton); buttonContainer.appendChild(submitButton);
}
this.container.appendChild(buttonContainer); this.container.appendChild(buttonContainer);
} }
@ -209,6 +213,7 @@ class SeatChooser {
this.selectedSeats.push(seat); this.selectedSeats.push(seat);
} }
} }
this.seatClickCallback(this, seat, this.selectedSeats.includes(seat));
} }
clearSelection() { clearSelection() {
@ -219,21 +224,3 @@ class SeatChooser {
this.selectedSeats = []; this.selectedSeats = [];
} }
} }
let seatChooser = new SeatChooser(1, document.getElementById('seat-chooser'), (sc) => {
// gray overlay and show selected seats
let overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100vw';
overlay.style.height = '100vh';
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.innerText = 'You selected the following seats: ' + sc.selectedSeats.map(seat => `${seat.row}-${seat.col}`).join(', ');
overlay.addEventListener('click', () => {
overlay.remove();
});
sc.container.appendChild(overlay);
});

View file

@ -0,0 +1,22 @@
<div>
<table>
<thead>
<tr>
<th>Order ID</th>
<th>Order Date</th>
<th>Order Status</th>
<th>Order Total</th>
</tr>
</thead>
<tbody>
@foreach ($orders as $order)
<tr>
<td><a href="/order/{{ $order->order_id }}">{{ $order->order_id }}</a></td>
<td>{{ $order->order_date }}</td>
<td>{{ $order->order_status }}</td>
<td>{{ $order->order_total }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>

View file

@ -3,7 +3,15 @@
@push('head') @push('head')
<link rel="stylesheet" href="{{ asset('css/generic.css') }}"> <link rel="stylesheet" href="{{ asset('css/generic.css') }}">
<link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}"> <link rel="stylesheet" href="{{ asset('css/seat-chooser.css') }}">
<script src="{{ asset('js/seat-chooser.js') }}" defer></script> <script src="{{ asset('js/seat-chooser.js') }}"></script>
@endpush @endpush
<div id="seat-chooser"></div> <div>
<div id="seat-chooser" data-showing-id="{{ $showing_id }}">
<div id="seat-chooser-status">
<h2>Loading...</h2>
<span id="seat-chooser-status-message">Fetching prices...</span>
</div>
</div>
</div>

View file

@ -14,6 +14,10 @@
<hr/> <hr/>
@endif @endif
<h2>Orders</h2>
<hr/>
<x-orders :orders="Auth()->user()->orders" />
<h2>Permissions</h2> <h2>Permissions</h2>
<hr/> <hr/>

View file

@ -4,6 +4,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ isset($title) ? $title : 'Page' }} - {{ config('app.name', 'CineFlex') }} Manage</title> <title>{{ isset($title) ? $title : 'Page' }} - {{ config('app.name', 'CineFlex') }} Manage</title>
<script src="{{asset('js/freq.js')}}"></script>
@stack('head') @stack('head')
</head> </head>

View file

@ -7,8 +7,8 @@
<ul> <ul>
@foreach($cinemas as $cinema) @foreach($cinemas as $cinema)
<li> <li>
<a href="{{ route('cinema', ['id' => $cinema->id]) }}"> <a href="{{ route('cinema', ['id' => $cinema->cinema_id]) }}">
{{ $cinema->name }} {{ $cinema->cinema_name }}
</a> </a>
</li> </li>
@endforeach @endforeach

View file

@ -10,8 +10,15 @@
<h1>Now playing:</h1> <h1>Now playing:</h1>
<hr/> <hr/>
<div id="movies"> <div id="movies">
@foreach($movies as $showing) @foreach($movies as $movie)
<a class="movie" href="{{route('movie', ['id' => $movie->movie_id])}}">
<img src="{{ $movie->movie_image }}" alt="{{ $movie->movie_name }}">
<div class="details">
<h2>{{ $movie->movie_name }}</h2>
<span>{{ $movie->movie_description }}</span>
<p>{{ $movie->movie_length }} minutes</p>
</div>
</a>
@endforeach @endforeach
</section> </section>

View file

@ -0,0 +1,29 @@
@extends('main.layout')
@section('content')
<section>
<h1>{{$movie->movie_name}}</h1>
<hr/>
<div id="movie">
<img src="{{$movie->movie_image}}" alt="{{$movie->movie_name}} Poster" width="200px">
<div class="details">
<span>{{$movie->movie_length}} min</span><br/>
<span>{{$movie->movie_description}}</span>
</div>
</div>
</section>
<section>
<h1>Now playing:</h1>
<hr/>
<div id="showings">
@foreach($movie->showings as $showing)
<a href="/showing/{{$showing->showing_id}}">
<h2>{{$showing->room->cinema->cinema_name}} @ {{$showing->showing_start}} - {{$showing->showing_end()}}</h2>
</a>
@endforeach
</div>
</section>
@endsection

View file

@ -0,0 +1,35 @@
@extends('layout')
@push('head')
<link rel="stylesheet" href="{{asset('css/generic.css')}}">
<link rel="stylesheet" href="{{asset('css/order.css')}}">
<script src="{{asset('js/order.js')}}" defer></script>
@endpush
@section('body')
<h1>Ordering tickets for {{ $showing->movie->movie_name }}</h1>
<hr/>
<div id="showing">
<div class="details">
<span>Cinema: <a
href="/cinema/{{$showing->room->cinema->cinema_id}}">{{$showing->room->cinema->cinema_name}}</a></span><br/>
<span>Room: {{$showing->room->room_name}}</span><br/>
<span>Showing: {{$showing->showing_start}} - {{$showing->showing_end()}}</span><br/>
</div>
</div>
<hr/>
@component('components.seat-chooser', ['room_id' => $showing->room->room_id, 'showing_id' => $showing->showing_id])
@endcomponent
<hr/>
<div id="summary">
<h2>Summary</h2>
<hr/>
<div id="summary-dyn">
No seats selected.
</div>
<hr/>
<div id="summary-total">Total: €0.00</div>
</div>
<button id="order-button" class="button" disabled=true>Order</button>
@endsection

View file

@ -10,7 +10,7 @@
<div class="details"> <div class="details">
<span>Cinema: <a <span>Cinema: <a
href="/cinema/{{$showing->room->cinema->cinema_id}}">{{$showing->room->cinema->cinema_name}}</a></span><br/> href="/cinema/{{$showing->room->cinema->cinema_id}}">{{$showing->room->cinema->cinema_name}}</a></span><br/>
<span>Showing: {{$showing->showing_start}} - {{$showing->end_time()}}</span><br/> <span>Showing: {{$showing->showing_start}} - {{$showing->showing_end()}}</span><br/>
</div> </div>
</div> </div>
</section> </section>
@ -30,8 +30,7 @@
<section> <section>
<h1>Tickets:</h1> <h1>Tickets:</h1>
<hr/> <hr/>
<span>Tickets available: {{$showing->tickets_available()}}</span><br/> <span>Tickets available: {{$showing->tickets_available()}}</span><br/><br/>
<span>Buy tickets?</span> <a href="/showing/{{$showing->showing_id}}/order" class="button">Order Tickets</a>
{{-- todo--}}
</section> </section>
@endsection @endsection

View file

@ -3,46 +3,27 @@
@section('content') @section('content')
<h1>Cinemas</h1> <h1>Cinemas</h1>
<hr/><br/> <hr/><br/>
<h2>Stats:</h2> <h2>Cinemas ({{ count(Auth::user()->cinemas()) }} / {{ $cinemas->count() }})</h2>
<div class="spread-h"> <div>
<a class="clicky-card" href="{{ route('manage.cinemas') }}"> @if (count(Auth::user()->cinemas()) > 0)
<div class="card"> <ul>
<h3>Amount Cinemas</h3> @foreach(Auth::user()->cinemas() as $cinema)
<span class="big-stat">{{ $cinemas->count() }}</span> <li>
</div> <a href="{{ route('manage.cinema', ['id' => $cinema->cinema_id]) }}">
</a> <div>
<a class="clicky-card" href="{{ route('manage.cinemas.employees') }}"> <h3>{{ $cinema->cinema_name }}</h3>
<div class="card">
<h3>Amount Employees</h3>
<span class="big-stat">{{ $employees->count() }}</span>
</div>
</a>
</div>
<br/>
<h2>Cinemas:</h2>
<div class="spread-h">
@foreach($cinemas as $cinema)
<a class="clicky-card" href="{{ route('manage.cinemas.show', ['id' => $cinema->id]) }}">
<div class="card">
<h3>{{ $cinema->name }}</h3>
<span class="big-stat">{{ $cinema->address->address_city }}</span> <span class="big-stat">{{ $cinema->address->address_city }}</span>
</div> </div>
</a> </a>
</li>
@endforeach @endforeach
</ul>
@else
<div class="warning">
<h3>No Cinemas</h3>
<span>Did you have permission?</span>
</div>
@endif
</div> </div>
<br/> <br/>
<h2>Employees:</h2>
<div class="spread-h">
@foreach($employees as $employee)
<a class="clicky-card" href="{{ route('manage.cinemas.employees.show', ['id' => $employee->id]) }}">
<div class="card">
<h3>{{ $employee->name }}</h3>
<span class="big-stat">{{ $employee->cinema->name }}</span>
</div>
</a>
@endforeach
</div>
<br/>
<a class="btn btn-primary" href="{{ route('manage') }}">Back</a>
@endsection @endsection

View file

@ -5,7 +5,7 @@
<hr/><br/> <hr/><br/>
<h2>Stats:</h2> <h2>Stats:</h2>
<div class="spread-h"> <div class="spread-h">
<a class="clicky-card" href="{{ route('manage') }}"> <a class="clicky-card" href="{{ route('manage.users') }}">
<div class="card"> <div class="card">
<h3>Amount Users</h3> <h3>Amount Users</h3>
<span class="big-stat">{{ $users->count() }}</span> <span class="big-stat">{{ $users->count() }}</span>
@ -23,7 +23,7 @@
<span class="big-stat">{{ $genres->count() }}</span> <span class="big-stat">{{ $genres->count() }}</span>
</div> </div>
</a> </a>
<a class="clicky-card" href="{{ route('manage') }}"> <a class="clicky-card" href="{{ route('manage.showings') }}">
<div class="card"> <div class="card">
<h3>Amount Showings</h3> <h3>Amount Showings</h3>
<span class="big-stat">{{ $showings->count() }}</span> <span class="big-stat">{{ $showings->count() }}</span>

View file

@ -18,12 +18,26 @@
</header> </header>
<div id="sidebar"> {{-- Page Aware --}} <div id="sidebar"> {{-- Page Aware --}}
<a class="{{ Request::is('manage') ? 'active' : '' }}" href="{{ route('manage') }}">Dashboard</a> <a class="{{ Request::is('manage') ? 'active' : '' }}" href="{{ route('manage') }}">Dashboard</a>
{{-- manage cinemas--}}
{{-- <a class="{{ Request::is('manage/cinemas') ? 'active' : '' }}" href="{{ route('manage.cinemas') }}">Cinemas</a>--}} @if (Auth::user()->hasPermission("READ_CINEMAS"))
{{-- <a class="{{ Request::is('manage/cinemas/employees') ? 'active' : '' }} child" href="{{ route('manage.cinemas.employees') }}">Employees</a>--}} <a class="{{ Request::is('manage/cinemas') ? 'active' : '' }}" href="{{ route('manage.cinemas') }}">Cinemas</a>
@endif
@if (Auth::user()->hasPermission("READ_MOVIES"))
<a class="{{ Request::is('manage/movies') ? 'active' : '' }}" href="{{ route('manage.movies') }}">Movies</a> <a class="{{ Request::is('manage/movies') ? 'active' : '' }}" href="{{ route('manage.movies') }}">Movies</a>
@endif
@if (Auth::user()->hasPermission("READ_SHOWINGS"))
<a class="child {{ Request::is('manage/showings') ? 'active' : '' }}" href="{{ route('manage.showings') }}">Showings</a> <a class="child {{ Request::is('manage/showings') ? 'active' : '' }}" href="{{ route('manage.showings') }}">Showings</a>
@endif
@if (Auth::user()->hasPermission("READ_GENRES"))
<a class="{{ Request::is('manage/genres') ? 'active' : '' }}" href="{{ route('manage.genres') }}">Genres</a> <a class="{{ Request::is('manage/genres') ? 'active' : '' }}" href="{{ route('manage.genres') }}">Genres</a>
@endif
@if(Auth::user()->atleast('manage') && Auth::user()->hasPermission("READ_USERS"))
<a class="{{ Request::is('manage/users') ? 'active' : '' }}" href="{{ route('manage.users') }}">Users</a>
@endif
</div> </div>
<main> <main>
@yield('content') @yield('content')

View file

@ -18,7 +18,7 @@
<h3>Times</h3> <h3>Times</h3>
<span class="mono"> <span class="mono">
Start: {{ $showing->showing_start }}<br> Start: {{ $showing->showing_start }}<br>
Ends : {{ $showing->end_time() }} Ends : {{ $showing->showing_end() }}
</span> </span>
</div> </div>
<div class="actions"> <div class="actions">

View file

@ -0,0 +1,41 @@
@extends('manage.layout')
@section('content')
<h1>Users</h1>
<hr/>
<div id="users">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
@foreach($users as $user)
<tr>
<td>
<a href="{{ route('manage.user', ['id' => $user->user_id]) }}">
{{ $user->name }}
</a>
</td>
<td>
<a href="{{ route('manage.user', ['id' => $user->user_id]) }}">
{{ $user->email }}
</a>
</td>
<td>
<a href="{{ route('manage.user', ['id' => $user->user_id]) }}">
{{ $user->role }}
</a>
</td>
<td>
<a href="{{ route('manage.user', ['id' => $user->user_id]) }}">
View
</a>
</td>
</tr>
@endforeach
</table>
</div>
@endsection

View file

@ -0,0 +1,117 @@
@extends('manage.layout')
@section('content')
<h1>User: {{ $user->name }}</h1>
<hr/>
<h2>Core User Details</h2>
<form id="core-user-form" class="form" method="POST" action="{{ route('manage.user', ['id' => $user->user_id ]) }}">
@csrf
<div class="form-group">
<label for="name">Name</label>
<input type="text" name="name" id="name" value="{{ $user->name }}"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" placeholder="Change password" name="password" id="password"/>
</div>
<div class="form-group">
<label for="role">Role</label>
<select>
<option value="admin" {{ $user->role == 'admin' ? 'selected' : '' }}>Admin</option>
</select>
</div>
<div class="form-group">
<button type="submit">Update</button>
</div>
</form>
<script id="core-user-form-loader">
// request allowed roles to give from the API (/api/allowedRoles)
// populate the select with the allowed roles
// set the selected role to the current role
</script>
<h2>User Permissions</h2>
{{-- for loop, and make sure that only if the user has the DELETE_PERMISSION that they can delete it and if they have the UPDATE_PERMISSION that they can update it--}}
<div id="permissions">
<table id="permissions">
<thead>
<tr>
<th>Permission</th>
<th>Actions</th>
</tr>
</thead>
@foreach(auth()->user()->permissions as $permission)
<tr>
<td>{{ $permission->permission_name }}</td>
<td>
<form action="{{ route('api.user.permission', ['id' => $user->user_id, 'permission_id' => $permission->permission_id]) }}" method="DELETE">
@csrf
@method('DELETE')
<button type="submit">Delete</button>
</form>
</td>
</tr>
@endforeach
<form id="add-permission" method="POST"
action="{{ route('api.user.permission', ['id' => $user->user_id ]) }}">
@csrf
<tr>
<td>
<input class="text" type="text" name="permission_name" id="permission"/>
</td>
<td>
<button class="button" type="submit">Add</button>
</td>
</tr>
</form>
</table>
<h2>Cinema Assignments</h2>
<table id="assignments">
<thead>
<tr>
<th>Assignment</th>
<th>Actions</th>
</tr>
</thead>
@foreach(auth()->user()->cinemas() as $cinema)
<tr>
<td>{{ $cinema->cinema_name }}</td>
<td>
@if($user->hasPermission('DELETE_CINEMA_ASSIGNMENT'))
<a href="{{ route('api.user.assignment', ['id' => $cinema->cinema_id]) }}">
Delete
</a>
@endif
</td>
</tr>
@endforeach
@if ($user->hasPermission('UPDATE_USER'))
<form id="add-assignment" method="POST"
action="{{ route('api.user.assignment', ['id' => $user->user_id ]) }}">
@csrf
<tr>
<td>
<select name="cinema" id="cinema">
@foreach(auth()->user()->cinemas() as $cinema)
<option value="{{ $cinema->cinema_id }}">{{ $cinema->cinema_name }}</option>
@endforeach
</select>
</td>
<td>
<button class="button" type="submit">Add</button>
</td>
</tr>
</form>
@endif
</table>
</div>
@endsection

View file

@ -14,6 +14,7 @@
| |
*/ */
Route::middleware('auth:sanctum')->get('/user', function (Request $request) { Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user(); return $request->user();
}); });
@ -42,3 +43,72 @@
$showing = (new App\Models\Showing)->find($id); $showing = (new App\Models\Showing)->find($id);
return $showing->seatMatrix(); return $showing->seatMatrix();
}); });
// /api/showing/{id}/prices
Route::get('/showing/{id}/prices', function ($id) {
$showing = (new App\Models\Showing)->find($id);
return $showing->prices;
});
// /api/user/{id}/permissions
Route::get('/user/{id}/permissions', function ($id) {
$user = (new App\Models\User)->find($id);
return $user->permissions;
})->name('api.user.permissions');
Route::post('/user/{id}/permission', function ($id, Request $request) {
$user = (new App\Models\User)->find($id);
// check if request is valid
if (!isset($request->permission_name)) {
return response()->json(['error' => 'permission name not set'], 400);
}
// check if permission exists
// if it does not exist, create it
$permission = (new App\Models\Permission)->where('permission_name', $request->permission_name);
if ($permission->count() == 0) {
$permission = (new App\Models\Permission)->create(['permission_name' => $request->permission_name]);
} else {
$permission = $permission->first();
}
// check if user has permission
// if not, add it
$user->permissions()->attach($permission->permission_id);
$user->save();
return $user->permissions;
})->name('api.user.permission');
Route::delete('/user/{id}/permission', function ($id, Request $request) {
$user = (new App\Models\User)->find($id);
$perm = (new App\Models\Permission())->find(request('permission_id'));
$user->permissions->detach($perm);
$user->save();
return $user->permissions;
})->name('api.user.permission');
// /api/user/{id}/assignments
Route::get('/user/{id}/assignments', function ($id) {
$user = (new App\Models\User)->find($id);
return $user->cinemas;
})->name('api.user.assignments');
Route::post('/user/{id}/assignment', function ($id) {
$user = (new App\Models\User)->find($id);
$cinema = (new App\Models\Cinema())->find(request('cinema_id'));
$user->cinemas->attach($cinema);
$user->save();
return $user->cinemas;
})->name('api.user.assignment');
Route::delete('/user/{id}/assignment', function ($id) {
$user = (new App\Models\User)->find($id);
$cinema = (new App\Models\Cinema())->find(request('cinema_id'));
$user->cinemas->detach($cinema);
$user->save();
return $user->cinemas;
})->name('api.user.assignment');

View file

@ -46,8 +46,9 @@
Route::get('/showings', [App\Http\Controllers\Main\ShowingController::class, 'showAllShowings'])->name('showings'); Route::get('/showings', [App\Http\Controllers\Main\ShowingController::class, 'showAllShowings'])->name('showings');
Route::get('/showing/{id}', [App\Http\Controllers\Main\ShowingController::class, 'show'])->name('showing'); Route::get('/showing/{id}', [App\Http\Controllers\Main\ShowingController::class, 'show'])->name('showing');
Route::get('/showing/{id}/order', [App\Http\Controllers\Main\ShowingController::class, 'order'])->name('showing.order');
Route::get('/movies', [App\Http\Controllers\Main\MovieController::class, 'showAllMovies'])->name('movies'); Route::get('/movies', [App\Http\Controllers\Main\MovieController::class, 'moviesNowShowing'])->name('movies');
Route::get('/movie/{id}', [App\Http\Controllers\Main\MovieController::class, 'show'])->name('movie'); Route::get('/movie/{id}', [App\Http\Controllers\Main\MovieController::class, 'show'])->name('movie');
Route::get('/genres', [App\Http\Controllers\Main\GenreController::class, 'showAllGenres'])->name('genres'); Route::get('/genres', [App\Http\Controllers\Main\GenreController::class, 'showAllGenres'])->name('genres');
@ -69,12 +70,11 @@
})->name('manage'); })->name('manage');
Route::controller(\App\Http\Controllers\Managing\MovieController::class)->group(function () { Route::controller(\App\Http\Controllers\Managing\MovieController::class)->group(function () {
// /manage/movies - CR movies (FB) // /manage/movies - CR movies (FB)
Route::get('/manage/movies', [\App\Http\Controllers\Managing\MovieController::class, 'showAllMovies'])->name('manage.movies'); Route::get('/manage/movies', [\App\Http\Controllers\Managing\MovieController::class, 'index'])->name('manage.movies');
Route::post('/manage/movies', [\App\Http\Controllers\Managing\MovieController::class, 'store'])->name('manage.movies'); Route::post('/manage/movies', [\App\Http\Controllers\Managing\MovieController::class, 'store'])->name('manage.movies');
// /manage/movies/create - C movie (F) // /manage/movies/create - C movie (F)
@ -89,7 +89,7 @@
Route::controller(\App\Http\Controllers\Managing\GenreController::class)->group(function () { Route::controller(\App\Http\Controllers\Managing\GenreController::class)->group(function () {
// /manage/genres - CR genres (FB) // /manage/genres - CR genres (FB)
Route::get('/manage/genres', [\App\Http\Controllers\Managing\GenreController::class, 'showAllGenres'])->name('manage.genres'); Route::get('/manage/genres', [\App\Http\Controllers\Managing\GenreController::class, 'index'])->name('manage.genres');
Route::post('/manage/genres', [\App\Http\Controllers\Managing\GenreController::class, 'store'])->name('manage.genres'); Route::post('/manage/genres', [\App\Http\Controllers\Managing\GenreController::class, 'store'])->name('manage.genres');
// /manage/genres/create - C genre (F) // /manage/genres/create - C genre (F)
@ -104,7 +104,7 @@
Route::controller(\App\Http\Controllers\Managing\CinemaController::class)->group(function () { Route::controller(\App\Http\Controllers\Managing\CinemaController::class)->group(function () {
// /manage/cinemas - CR cinemas (FB) // /manage/cinemas - CR cinemas (FB)
Route::get('/manage/cinemas', [\App\Http\Controllers\Managing\CinemaController::class, 'showAllCinemas'])->name('manage.cinemas'); Route::get('/manage/cinemas', [\App\Http\Controllers\Managing\CinemaController::class, 'index'])->name('manage.cinemas');
Route::post('/manage/cinemas', [\App\Http\Controllers\Managing\CinemaController::class, 'store'])->name('manage.cinemas'); Route::post('/manage/cinemas', [\App\Http\Controllers\Managing\CinemaController::class, 'store'])->name('manage.cinemas');
// /manage/cinemas/create - C cinema (F) // /manage/cinemas/create - C cinema (F)
@ -119,7 +119,7 @@
Route::controller(\App\Http\Controllers\Managing\ShowingsController::class)->group(function () { Route::controller(\App\Http\Controllers\Managing\ShowingsController::class)->group(function () {
// /manage/showings - CR showings (FB) // /manage/showings - CR showings (FB)
Route::get('/manage/showings', [\App\Http\Controllers\Managing\ShowingsController::class, 'showAllShowings'])->name('manage.showings'); Route::get('/manage/showings', [\App\Http\Controllers\Managing\ShowingsController::class, 'index'])->name('manage.showings');
Route::post('/manage/showings', [\App\Http\Controllers\Managing\ShowingsController::class, 'store'])->name('manage.showings'); Route::post('/manage/showings', [\App\Http\Controllers\Managing\ShowingsController::class, 'store'])->name('manage.showings');
// /manage/showings/create - C showing (F) // /manage/showings/create - C showing (F)
@ -132,13 +132,18 @@
Route::delete('/manage/showing/{id}', [\App\Http\Controllers\Managing\ShowingsController::class, 'destroy'])->name('manage.showing'); Route::delete('/manage/showing/{id}', [\App\Http\Controllers\Managing\ShowingsController::class, 'destroy'])->name('manage.showing');
}); });
// /test/comp/{component} Route::controller(\App\Http\Controllers\Managing\UsersController::class)->group(function () {
Route::get('/test/comp/{component}', function ($component) { // /manage/users - CR users (FB)
switch ($component) { Route::get('/manage/users', [\App\Http\Controllers\Managing\UsersController::class, 'index'])->name('manage.users');
case 'seat-chooser': Route::post('/manage/users', [\App\Http\Controllers\Managing\UsersController::class, 'store'])->name('manage.users');
$c = new \App\View\Components\SeatChooser(1, 1);
return $c->render(); // /manage/users/create - C user (F)
default: Route::get('/manage/users/create', [\App\Http\Controllers\Managing\UsersController::class, 'createUser'])->name('manage.users.create');
return "No component found"; // no post, handled by POST /users
}
})->name('test.comp'); // /manage/users/{id} - RUD user (FB)
Route::get('/manage/user/{id}', [\App\Http\Controllers\Managing\UsersController::class, 'show'])->name('manage.user');
Route::put('/manage/user/{id}', [\App\Http\Controllers\Managing\UsersController::class, 'update'])->name('manage.user');
Route::delete('/manage/user/{id}', [\App\Http\Controllers\Managing\UsersController::class, 'destroy'])->name('manage.user');
});

2
storage/debugbar/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore