Compare commits
10 commits
d9fbd5654b
...
1cb9d51c2f
Author | SHA1 | Date | |
---|---|---|---|
1cb9d51c2f | |||
71d401b138 | |||
7ec1c2b7f0 | |||
de6c64bb14 | |||
450dba1d74 | |||
9385a4b7f1 | |||
5f97900e30 | |||
350fdd3520 | |||
a3bebf3976 | |||
494fe698b0 |
49 changed files with 1475 additions and 163 deletions
|
@ -6,6 +6,7 @@
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
use Illuminate\Support\Facades\Request;
|
||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
|
|
||||||
class CategoryController extends Controller
|
class CategoryController extends Controller
|
||||||
|
@ -16,8 +17,10 @@ public function index()
|
||||||
{
|
{
|
||||||
$this->authorize('viewAny', Category::class);
|
$this->authorize('viewAny', Category::class);
|
||||||
|
|
||||||
return View::make('categories.index',
|
return View::make('categories.index', [
|
||||||
['categories' => Category::all()]);
|
'categories' => Category::all(),
|
||||||
|
'trashedCategories' => Category::onlyTrashed()->get()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
|
@ -33,6 +36,7 @@ public function store(CategoryRequest $request)
|
||||||
|
|
||||||
Category::create($request->validated());
|
Category::create($request->validated());
|
||||||
|
|
||||||
|
$request->session()->flash('status', 'Category added!');
|
||||||
return Redirect::route('categories.index');
|
return Redirect::route('categories.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +44,8 @@ public function show(Category $category)
|
||||||
{
|
{
|
||||||
$this->authorize('view', $category);
|
$this->authorize('view', $category);
|
||||||
|
|
||||||
|
$category->load('programs');
|
||||||
|
|
||||||
return View::make('categories.show', ['category' => $category]);
|
return View::make('categories.show', ['category' => $category]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +58,17 @@ public function edit(Category $category)
|
||||||
|
|
||||||
public function update(CategoryRequest $request, Category $category)
|
public function update(CategoryRequest $request, Category $category)
|
||||||
{
|
{
|
||||||
$this->authorize('update', $category);
|
if ($request->has('restore')) {
|
||||||
|
$this->authorize('restore', $category);
|
||||||
|
|
||||||
$category->update($request->validated());
|
$category->restore();
|
||||||
|
$request->session()->flash('status', 'Category restored!');
|
||||||
|
} else {
|
||||||
|
$this->authorize('update', $category);
|
||||||
|
|
||||||
|
$category->update($request->validated());
|
||||||
|
$request->session()->flash('status', 'Category updated!');
|
||||||
|
}
|
||||||
|
|
||||||
return Redirect::route('categories.index');
|
return Redirect::route('categories.index');
|
||||||
}
|
}
|
||||||
|
@ -65,6 +79,7 @@ public function destroy(Category $category)
|
||||||
|
|
||||||
$category->delete();
|
$category->delete();
|
||||||
|
|
||||||
return response()->json();
|
Request::session()->flash('status', 'Category deleted!');
|
||||||
|
return Redirect::route('categories.index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
52
app/Http/Controllers/ProgramController.php
Normal file
52
app/Http/Controllers/ProgramController.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\ProgramRequest;
|
||||||
|
use App\Models\Program;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
|
|
||||||
|
class ProgramController extends Controller
|
||||||
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->authorize('viewAny', Program::class);
|
||||||
|
|
||||||
|
return View::make('programs.index', ['programs' => Program::all()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(ProgramRequest $request)
|
||||||
|
{
|
||||||
|
$this->authorize('create', Program::class);
|
||||||
|
|
||||||
|
return Program::create($request->validated());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Program $program)
|
||||||
|
{
|
||||||
|
$this->authorize('view', $program);
|
||||||
|
|
||||||
|
return $program;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(ProgramRequest $request, Program $program)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $program);
|
||||||
|
|
||||||
|
$program->update($request->validated());
|
||||||
|
|
||||||
|
return $program;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Program $program)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $program);
|
||||||
|
|
||||||
|
$program->delete();
|
||||||
|
|
||||||
|
return response()->json();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
use App\Models\Category;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class CategoryRequest extends FormRequest
|
class CategoryRequest extends FormRequest
|
||||||
|
@ -10,18 +9,13 @@ class CategoryRequest extends FormRequest
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => ['required'],
|
'name' => ['sometimes', 'required', 'string'],
|
||||||
|
'restore' => ['sometimes', 'required', 'boolean']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
if ($this->routeIs('categories.store')) {
|
return true;
|
||||||
return $this->user()->can('create', Category::class);
|
|
||||||
} elseif ($this->routeIs('categories.update')) {
|
|
||||||
return $this->user()->can('update', $this->route('category'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
app/Http/Requests/ProgramRequest.php
Normal file
30
app/Http/Requests/ProgramRequest.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Models\Program;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ProgramRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required'],
|
||||||
|
'category_id' => ['required', 'exists:categories'],
|
||||||
|
'description' => ['nullable'],
|
||||||
|
'website' => ['nullable'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
if ($this->routeIs('programs.store')) {
|
||||||
|
return $this->user()->can('create', Program::class);
|
||||||
|
} elseif ($this->routeIs('programs.update')) {
|
||||||
|
return $this->user()->can('update', $this->route('program'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
class Category extends Model
|
class Category extends Model
|
||||||
|
@ -13,4 +14,9 @@ class Category extends Model
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function programs(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Program::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
26
app/Models/Program.php
Normal file
26
app/Models/Program.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class Program extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use SoftDeletes;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'category_id',
|
||||||
|
'description',
|
||||||
|
'website',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function category(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Category::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,12 +11,12 @@ class CategoryPolicy
|
||||||
{
|
{
|
||||||
use HandlesAuthorization;
|
use HandlesAuthorization;
|
||||||
|
|
||||||
public function viewAny(User $user): bool
|
public function viewAny(?User $user): bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function view(User $user, Category $category): bool
|
public function view(?User $user, Category $category): bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
48
app/Policies/ProgramPolicy.php
Normal file
48
app/Policies/ProgramPolicy.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Program;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class ProgramPolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Program $program): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Program $program): bool
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Program $program): bool
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restore(User $user, Program $program): bool
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forceDelete(User $user, Program $program): bool
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,19 +9,20 @@
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.4",
|
||||||
"laravel/framework": "^11.31",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.9"
|
"laravel/tinker": "^2.10.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"defstudio/pest-plugin-laravel-expectations": "^2.4",
|
||||||
"laravel/breeze": "^2.3",
|
"fakerphp/faker": "^1.24.1",
|
||||||
"laravel/pail": "^1.1",
|
"laravel/breeze": "^2.3.5",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pail": "^1.2.2",
|
||||||
"laravel/sail": "^1.26",
|
"laravel/pint": "^1.21",
|
||||||
"mockery/mockery": "^1.6",
|
"laravel/sail": "^1.41",
|
||||||
"nunomaduro/collision": "^8.1",
|
"mockery/mockery": "^1.6.12",
|
||||||
"pestphp/pest": "^3.7",
|
"nunomaduro/collision": "^8.6.1",
|
||||||
|
"pestphp/pest": "^3.7.4",
|
||||||
"pestphp/pest-plugin-laravel": "^3.1"
|
"pestphp/pest-plugin-laravel": "^3.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
|
225
composer.lock
generated
225
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "a739267a391adcba09c97097cbad9f52",
|
"content-hash": "eb1e2c5c0d22bcee0ed64e63a3619f9f",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
|
@ -1056,20 +1056,20 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v11.43.0",
|
"version": "v12.0.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/framework.git",
|
"url": "https://github.com/laravel/framework.git",
|
||||||
"reference": "70760d976486310b11d8e487e873077db069e77a"
|
"reference": "d99e2385a6d4324782d52f4423891966425641be"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/framework/zipball/70760d976486310b11d8e487e873077db069e77a",
|
"url": "https://api.github.com/repos/laravel/framework/zipball/d99e2385a6d4324782d52f4423891966425641be",
|
||||||
"reference": "70760d976486310b11d8e487e873077db069e77a",
|
"reference": "d99e2385a6d4324782d52f4423891966425641be",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"brick/math": "^0.9.3|^0.10.2|^0.11|^0.12",
|
"brick/math": "^0.11|^0.12",
|
||||||
"composer-runtime-api": "^2.2",
|
"composer-runtime-api": "^2.2",
|
||||||
"doctrine/inflector": "^2.0.5",
|
"doctrine/inflector": "^2.0.5",
|
||||||
"dragonmantank/cron-expression": "^3.4",
|
"dragonmantank/cron-expression": "^3.4",
|
||||||
|
@ -1084,32 +1084,32 @@
|
||||||
"fruitcake/php-cors": "^1.3",
|
"fruitcake/php-cors": "^1.3",
|
||||||
"guzzlehttp/guzzle": "^7.8.2",
|
"guzzlehttp/guzzle": "^7.8.2",
|
||||||
"guzzlehttp/uri-template": "^1.0",
|
"guzzlehttp/uri-template": "^1.0",
|
||||||
"laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
|
"laravel/prompts": "^0.3.0",
|
||||||
"laravel/serializable-closure": "^1.3|^2.0",
|
"laravel/serializable-closure": "^1.3|^2.0",
|
||||||
"league/commonmark": "^2.6",
|
"league/commonmark": "^2.6",
|
||||||
"league/flysystem": "^3.25.1",
|
"league/flysystem": "^3.25.1",
|
||||||
"league/flysystem-local": "^3.25.1",
|
"league/flysystem-local": "^3.25.1",
|
||||||
"league/uri": "^7.5.1",
|
"league/uri": "^7.5.1",
|
||||||
"monolog/monolog": "^3.0",
|
"monolog/monolog": "^3.0",
|
||||||
"nesbot/carbon": "^2.72.6|^3.8.4",
|
"nesbot/carbon": "^3.8.4",
|
||||||
"nunomaduro/termwind": "^2.0",
|
"nunomaduro/termwind": "^2.0",
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"psr/container": "^1.1.1|^2.0.1",
|
"psr/container": "^1.1.1|^2.0.1",
|
||||||
"psr/log": "^1.0|^2.0|^3.0",
|
"psr/log": "^1.0|^2.0|^3.0",
|
||||||
"psr/simple-cache": "^1.0|^2.0|^3.0",
|
"psr/simple-cache": "^1.0|^2.0|^3.0",
|
||||||
"ramsey/uuid": "^4.7",
|
"ramsey/uuid": "^4.7",
|
||||||
"symfony/console": "^7.0.3",
|
"symfony/console": "^7.2.0",
|
||||||
"symfony/error-handler": "^7.0.3",
|
"symfony/error-handler": "^7.2.0",
|
||||||
"symfony/finder": "^7.0.3",
|
"symfony/finder": "^7.2.0",
|
||||||
"symfony/http-foundation": "^7.2.0",
|
"symfony/http-foundation": "^7.2.0",
|
||||||
"symfony/http-kernel": "^7.0.3",
|
"symfony/http-kernel": "^7.2.0",
|
||||||
"symfony/mailer": "^7.0.3",
|
"symfony/mailer": "^7.2.0",
|
||||||
"symfony/mime": "^7.0.3",
|
"symfony/mime": "^7.2.0",
|
||||||
"symfony/polyfill-php83": "^1.31",
|
"symfony/polyfill-php83": "^1.31",
|
||||||
"symfony/process": "^7.0.3",
|
"symfony/process": "^7.2.0",
|
||||||
"symfony/routing": "^7.0.3",
|
"symfony/routing": "^7.2.0",
|
||||||
"symfony/uid": "^7.0.3",
|
"symfony/uid": "^7.2.0",
|
||||||
"symfony/var-dumper": "^7.0.3",
|
"symfony/var-dumper": "^7.2.0",
|
||||||
"tijsverkoyen/css-to-inline-styles": "^2.2.5",
|
"tijsverkoyen/css-to-inline-styles": "^2.2.5",
|
||||||
"vlucas/phpdotenv": "^5.6.1",
|
"vlucas/phpdotenv": "^5.6.1",
|
||||||
"voku/portable-ascii": "^2.0.2"
|
"voku/portable-ascii": "^2.0.2"
|
||||||
|
@ -1173,17 +1173,17 @@
|
||||||
"league/flysystem-read-only": "^3.25.1",
|
"league/flysystem-read-only": "^3.25.1",
|
||||||
"league/flysystem-sftp-v3": "^3.25.1",
|
"league/flysystem-sftp-v3": "^3.25.1",
|
||||||
"mockery/mockery": "^1.6.10",
|
"mockery/mockery": "^1.6.10",
|
||||||
"orchestra/testbench-core": "^9.9.4",
|
"orchestra/testbench-core": "^10.0",
|
||||||
"pda/pheanstalk": "^5.0.6",
|
"pda/pheanstalk": "^5.0.6",
|
||||||
"php-http/discovery": "^1.15",
|
"php-http/discovery": "^1.15",
|
||||||
"phpstan/phpstan": "^2.0",
|
"phpstan/phpstan": "^2.0",
|
||||||
"phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1",
|
"phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1",
|
||||||
"predis/predis": "^2.3",
|
"predis/predis": "^2.3",
|
||||||
"resend/resend-php": "^0.10.0",
|
"resend/resend-php": "^0.10.0",
|
||||||
"symfony/cache": "^7.0.3",
|
"symfony/cache": "^7.2.0",
|
||||||
"symfony/http-client": "^7.0.3",
|
"symfony/http-client": "^7.2.0",
|
||||||
"symfony/psr-http-message-bridge": "^7.0.3",
|
"symfony/psr-http-message-bridge": "^7.2.0",
|
||||||
"symfony/translation": "^7.0.3"
|
"symfony/translation": "^7.2.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
|
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
|
||||||
|
@ -1209,22 +1209,22 @@
|
||||||
"mockery/mockery": "Required to use mocking (^1.6).",
|
"mockery/mockery": "Required to use mocking (^1.6).",
|
||||||
"pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
|
"pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
|
||||||
"php-http/discovery": "Required to use PSR-7 bridging features (^1.15).",
|
"php-http/discovery": "Required to use PSR-7 bridging features (^1.15).",
|
||||||
"phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).",
|
"phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).",
|
||||||
"predis/predis": "Required to use the predis connector (^2.3).",
|
"predis/predis": "Required to use the predis connector (^2.3).",
|
||||||
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
|
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
|
||||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
|
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
|
||||||
"resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).",
|
"resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).",
|
||||||
"symfony/cache": "Required to PSR-6 cache bridge (^7.0).",
|
"symfony/cache": "Required to PSR-6 cache bridge (^7.2).",
|
||||||
"symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).",
|
"symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).",
|
||||||
"symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).",
|
"symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).",
|
||||||
"symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).",
|
"symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).",
|
||||||
"symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).",
|
"symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).",
|
||||||
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)."
|
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)."
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "11.x-dev"
|
"dev-master": "12.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -1267,7 +1267,7 @@
|
||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2025-02-18T15:37:56+00:00"
|
"time": "2025-02-24T13:31:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/prompts",
|
"name": "laravel/prompts",
|
||||||
|
@ -2111,16 +2111,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "nesbot/carbon",
|
||||||
"version": "3.8.5",
|
"version": "3.8.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||||
"reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4"
|
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/b1a53a27898639579a67de42e8ced5d5386aa9a4",
|
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
|
||||||
"reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4",
|
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2213,7 +2213,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-02-11T16:28:45+00:00"
|
"time": "2025-02-20T17:33:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nette/schema",
|
"name": "nette/schema",
|
||||||
|
@ -5893,6 +5893,103 @@
|
||||||
],
|
],
|
||||||
"time": "2024-12-11T14:50:44+00:00"
|
"time": "2024-12-11T14:50:44+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "defstudio/pest-plugin-laravel-expectations",
|
||||||
|
"version": "v2.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/defstudio/pest-plugin-laravel-expectations.git",
|
||||||
|
"reference": "4bfa314db13cba3271e25cb571aa8e8f73f8a2b4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/defstudio/pest-plugin-laravel-expectations/zipball/4bfa314db13cba3271e25cb571aa8e8f73f8a2b4",
|
||||||
|
"reference": "4bfa314db13cba3271e25cb571aa8e8f73f8a2b4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/contracts": "^10.0|^11.0.3|^12.0",
|
||||||
|
"illuminate/database": "^10.0|^11.0.3|^12.0",
|
||||||
|
"illuminate/http": "^10.0|^11.0.3|^12.0",
|
||||||
|
"illuminate/support": "^10.0|^11.0.3|^12.0",
|
||||||
|
"illuminate/testing": "^10.0|^11.0.3|^12.0",
|
||||||
|
"pestphp/pest": "^2.0|^3.0",
|
||||||
|
"pestphp/pest-plugin": "^2.0|^3.0",
|
||||||
|
"pestphp/pest-plugin-laravel": "^2.0|^3.0",
|
||||||
|
"php": "^8.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ergebnis/phpstan-rules": "^2.1.0",
|
||||||
|
"laravel/pint": "^1.11.0",
|
||||||
|
"nesbot/carbon": "^2.62.1",
|
||||||
|
"orchestra/testbench": "^8.0|^9.0",
|
||||||
|
"phpstan/phpstan": "^1.10.29",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1.5.1",
|
||||||
|
"rector/rector": "^1.0.3",
|
||||||
|
"symfony/var-dumper": "^6.3.3|^v7.0.4",
|
||||||
|
"symplify/phpstan-rules": "^12.1.4.72",
|
||||||
|
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/Autoload.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"DefStudio\\PestLaravelExpectations\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabio Ivona",
|
||||||
|
"email": "fabio.ivona@defstudio.it",
|
||||||
|
"homepage": "https://defstudio.it",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Daniele Romeo",
|
||||||
|
"email": "danieleromeo@defstudio.it",
|
||||||
|
"homepage": "https://defstudio.it",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A plugin to add laravel tailored expectations to Pest",
|
||||||
|
"keywords": [
|
||||||
|
"expectations",
|
||||||
|
"framework",
|
||||||
|
"laravel",
|
||||||
|
"pest",
|
||||||
|
"php",
|
||||||
|
"plugin",
|
||||||
|
"test",
|
||||||
|
"testing",
|
||||||
|
"unit"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/defstudio/pest-plugin-laravel-expectations/issues",
|
||||||
|
"source": "https://github.com/defstudio/pest-plugin-laravel-expectations/tree/v2.4.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/defstudio",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabio-ivona",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-22T11:50:10+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
|
@ -6245,29 +6342,29 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/breeze",
|
"name": "laravel/breeze",
|
||||||
"version": "v2.3.4",
|
"version": "v2.3.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/breeze.git",
|
"url": "https://github.com/laravel/breeze.git",
|
||||||
"reference": "e456fe0db93d1f9f5ce3b2043739a0777404395c"
|
"reference": "1d85805c4aecc425a0ce157147384d4becea3fa2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/breeze/zipball/e456fe0db93d1f9f5ce3b2043739a0777404395c",
|
"url": "https://api.github.com/repos/laravel/breeze/zipball/1d85805c4aecc425a0ce157147384d4becea3fa2",
|
||||||
"reference": "e456fe0db93d1f9f5ce3b2043739a0777404395c",
|
"reference": "1d85805c4aecc425a0ce157147384d4becea3fa2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/console": "^11.0",
|
"illuminate/console": "^11.0|^12.0",
|
||||||
"illuminate/filesystem": "^11.0",
|
"illuminate/filesystem": "^11.0|^12.0",
|
||||||
"illuminate/support": "^11.0",
|
"illuminate/support": "^11.0|^12.0",
|
||||||
"illuminate/validation": "^11.0",
|
"illuminate/validation": "^11.0|^12.0",
|
||||||
"php": "^8.2.0",
|
"php": "^8.2.0",
|
||||||
"symfony/console": "^7.0"
|
"symfony/console": "^7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0|^12.0",
|
||||||
"orchestra/testbench-core": "^9.0",
|
"orchestra/testbench-core": "^9.0|^10.0",
|
||||||
"phpstan/phpstan": "^2.0"
|
"phpstan/phpstan": "^2.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
@ -6302,7 +6399,7 @@
|
||||||
"issues": "https://github.com/laravel/breeze/issues",
|
"issues": "https://github.com/laravel/breeze/issues",
|
||||||
"source": "https://github.com/laravel/breeze"
|
"source": "https://github.com/laravel/breeze"
|
||||||
},
|
},
|
||||||
"time": "2025-02-11T13:19:28+00:00"
|
"time": "2025-02-19T23:49:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/pail",
|
"name": "laravel/pail",
|
||||||
|
@ -7445,16 +7542,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
"version": "2.0.2",
|
"version": "2.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||||
"reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e"
|
"reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51087f87dcce2663e1fed4dfd4e56eccd580297e",
|
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
|
||||||
"reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e",
|
"reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -7486,29 +7583,29 @@
|
||||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.2"
|
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0"
|
||||||
},
|
},
|
||||||
"time": "2025-02-17T20:25:51+00:00"
|
"time": "2025-02-19T13:28:12+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "11.0.8",
|
"version": "11.0.9",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
"reference": "418c59fd080954f8c4aa5631d9502ecda2387118"
|
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118",
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
|
||||||
"reference": "418c59fd080954f8c4aa5631d9502ecda2387118",
|
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"nikic/php-parser": "^5.3.1",
|
"nikic/php-parser": "^5.4.0",
|
||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"phpunit/php-file-iterator": "^5.1.0",
|
"phpunit/php-file-iterator": "^5.1.0",
|
||||||
"phpunit/php-text-template": "^4.0.1",
|
"phpunit/php-text-template": "^4.0.1",
|
||||||
|
@ -7520,7 +7617,7 @@
|
||||||
"theseer/tokenizer": "^1.2.3"
|
"theseer/tokenizer": "^1.2.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11.5.0"
|
"phpunit/phpunit": "^11.5.2"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-pcov": "PHP extension that provides line coverage",
|
"ext-pcov": "PHP extension that provides line coverage",
|
||||||
|
@ -7558,7 +7655,7 @@
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -7566,7 +7663,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-12-11T12:34:27+00:00"
|
"time": "2025-02-25T13:26:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
|
@ -9080,7 +9177,7 @@
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "^8.2"
|
"php": "^8.4"
|
||||||
},
|
},
|
||||||
"platform-dev": {},
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.6.0"
|
||||||
|
|
23
database/factories/ProgramFactory.php
Normal file
23
database/factories/ProgramFactory.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Program;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class ProgramFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = Program::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->faker->word(),
|
||||||
|
'description' => $this->faker->text(),
|
||||||
|
'website' => $this->faker->url(),
|
||||||
|
'created_at' => Carbon::now(),
|
||||||
|
'updated_at' => Carbon::now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('programs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->foreignIdFor(Category::class)->constrained('categories');
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->string('website')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('programs');
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use App\Models\Program;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
@ -23,6 +24,15 @@ public function run(): void
|
||||||
'password' => 'password',
|
'password' => 'password',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Category::factory(20)->create();
|
Category::factory(20)
|
||||||
|
->has(Program::factory()->count(3))
|
||||||
|
->has(Program::factory()->trashed()->count(1))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
Category::factory(10)
|
||||||
|
->has(Program::factory()->count(1))
|
||||||
|
->has(Program::factory()->trashed()->count(1))
|
||||||
|
->trashed()
|
||||||
|
->create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
services:
|
services:
|
||||||
laravel.test:
|
laravel.test:
|
||||||
build:
|
build:
|
||||||
context: './vendor/laravel/sail/runtimes/8.4'
|
context: './docker/8.4'
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
WWWGROUP: '${WWWGROUP}'
|
WWWGROUP: '${WWWGROUP}'
|
||||||
|
@ -25,7 +25,7 @@ services:
|
||||||
- pgsql
|
- pgsql
|
||||||
- redis
|
- redis
|
||||||
- meilisearch
|
- meilisearch
|
||||||
- mailpit
|
# - mailpit
|
||||||
- selenium
|
- selenium
|
||||||
pgsql:
|
pgsql:
|
||||||
image: 'postgres:17'
|
image: 'postgres:17'
|
||||||
|
@ -38,7 +38,7 @@ services:
|
||||||
POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}'
|
POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}'
|
||||||
volumes:
|
volumes:
|
||||||
- 'sail-pgsql:/var/lib/postgresql/data'
|
- 'sail-pgsql:/var/lib/postgresql/data'
|
||||||
- './vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql'
|
- './docker/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql'
|
||||||
networks:
|
networks:
|
||||||
- sail
|
- sail
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
@ -86,13 +86,13 @@ services:
|
||||||
- 'http://127.0.0.1:7700/health'
|
- 'http://127.0.0.1:7700/health'
|
||||||
retries: 3
|
retries: 3
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
mailpit:
|
# mailpit:
|
||||||
image: 'axllent/mailpit:latest'
|
# image: 'axllent/mailpit:latest'
|
||||||
ports:
|
# ports:
|
||||||
- '${FORWARD_MAILPIT_PORT:-1025}:1025'
|
# - '${FORWARD_MAILPIT_PORT:-1025}:1025'
|
||||||
- '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025'
|
# - '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025'
|
||||||
networks:
|
# networks:
|
||||||
- sail
|
# - sail
|
||||||
selenium:
|
selenium:
|
||||||
image: selenium/standalone-chromium
|
image: selenium/standalone-chromium
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
|
|
70
docker/8.0/Dockerfile
Normal file
70
docker/8.0/Dockerfile
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
LABEL maintainer="Taylor Otwell"
|
||||||
|
|
||||||
|
ARG WWWGROUP
|
||||||
|
ARG NODE_VERSION=22
|
||||||
|
ARG POSTGRES_VERSION=17
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=UTC
|
||||||
|
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
|
||||||
|
ENV SUPERVISOR_PHP_USER="sail"
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
|
||||||
|
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y php8.0-cli php8.0-dev \
|
||||||
|
php8.0-pgsql php8.0-sqlite3 php8.0-gd php8.0-imagick \
|
||||||
|
php8.0-curl php8.0-memcached php8.0-mongodb \
|
||||||
|
php8.0-imap php8.0-mysql php8.0-mbstring \
|
||||||
|
php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap \
|
||||||
|
php8.0-intl php8.0-readline php8.0-pcov \
|
||||||
|
php8.0-msgpack php8.0-igbinary php8.0-ldap \
|
||||||
|
php8.0-redis php8.0-swoole php8.0-xdebug \
|
||||||
|
&& curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g npm \
|
||||||
|
&& npm install -g bun \
|
||||||
|
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
|
||||||
|
&& curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y yarn \
|
||||||
|
&& apt-get install -y mysql-client \
|
||||||
|
&& apt-get install -y postgresql-client-$POSTGRES_VERSION \
|
||||||
|
&& apt-get -y autoremove \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN update-alternatives --set php /usr/bin/php8.0
|
||||||
|
|
||||||
|
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.0
|
||||||
|
|
||||||
|
RUN userdel -r ubuntu
|
||||||
|
RUN groupadd --force -g $WWWGROUP sail
|
||||||
|
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||||
|
|
||||||
|
COPY start-container /usr/local/bin/start-container
|
||||||
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY php.ini /etc/php/8.0/cli/conf.d/99-sail.ini
|
||||||
|
RUN chmod +x /usr/local/bin/start-container
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-container"]
|
5
docker/8.0/php.ini
Normal file
5
docker/8.0/php.ini
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[PHP]
|
||||||
|
post_max_size = 100M
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
variables_order = EGPCS
|
||||||
|
pcov.directory = .
|
26
docker/8.0/start-container
Normal file
26
docker/8.0/start-container
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
|
||||||
|
echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$WWWUSER" ]; then
|
||||||
|
usermod -u $WWWUSER sail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /.composer ]; then
|
||||||
|
mkdir /.composer
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod -R ugo+rw /.composer
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
|
||||||
|
exec "$@"
|
||||||
|
else
|
||||||
|
exec gosu $WWWUSER "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
fi
|
14
docker/8.0/supervisord.conf
Normal file
14
docker/8.0/supervisord.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php]
|
||||||
|
command=%(ENV_SUPERVISOR_PHP_COMMAND)s
|
||||||
|
user=%(ENV_SUPERVISOR_PHP_USER)s
|
||||||
|
environment=LARAVEL_SAIL="1"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
69
docker/8.1/Dockerfile
Normal file
69
docker/8.1/Dockerfile
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
LABEL maintainer="Taylor Otwell"
|
||||||
|
|
||||||
|
ARG WWWGROUP
|
||||||
|
ARG NODE_VERSION=22
|
||||||
|
ARG POSTGRES_VERSION=17
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=UTC
|
||||||
|
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
|
||||||
|
ENV SUPERVISOR_PHP_USER="sail"
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
|
||||||
|
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y php8.1-cli php8.1-dev \
|
||||||
|
php8.1-pgsql php8.1-sqlite3 php8.1-gd php8.1-imagick \
|
||||||
|
php8.1-curl php8.1-mongodb \
|
||||||
|
php8.1-imap php8.1-mysql php8.1-mbstring \
|
||||||
|
php8.1-xml php8.1-zip php8.1-bcmath php8.1-soap \
|
||||||
|
php8.1-intl php8.1-readline \
|
||||||
|
php8.1-ldap \
|
||||||
|
php8.1-msgpack php8.1-igbinary php8.1-redis php8.1-swoole \
|
||||||
|
php8.1-memcached php8.1-pcov php8.1-xdebug \
|
||||||
|
&& curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g npm \
|
||||||
|
&& npm install -g bun \
|
||||||
|
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarn.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
|
||||||
|
&& curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y yarn \
|
||||||
|
&& apt-get install -y mysql-client \
|
||||||
|
&& apt-get install -y postgresql-client-$POSTGRES_VERSION \
|
||||||
|
&& apt-get -y autoremove \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.1
|
||||||
|
|
||||||
|
RUN userdel -r ubuntu
|
||||||
|
RUN groupadd --force -g $WWWGROUP sail
|
||||||
|
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||||
|
|
||||||
|
COPY start-container /usr/local/bin/start-container
|
||||||
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY php.ini /etc/php/8.1/cli/conf.d/99-sail.ini
|
||||||
|
RUN chmod +x /usr/local/bin/start-container
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-container"]
|
5
docker/8.1/php.ini
Normal file
5
docker/8.1/php.ini
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[PHP]
|
||||||
|
post_max_size = 100M
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
variables_order = EGPCS
|
||||||
|
pcov.directory = .
|
26
docker/8.1/start-container
Normal file
26
docker/8.1/start-container
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
|
||||||
|
echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$WWWUSER" ]; then
|
||||||
|
usermod -u $WWWUSER sail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /.composer ]; then
|
||||||
|
mkdir /.composer
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod -R ugo+rw /.composer
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
|
||||||
|
exec "$@"
|
||||||
|
else
|
||||||
|
exec gosu $WWWUSER "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
fi
|
14
docker/8.1/supervisord.conf
Normal file
14
docker/8.1/supervisord.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php]
|
||||||
|
command=%(ENV_SUPERVISOR_PHP_COMMAND)s
|
||||||
|
user=%(ENV_SUPERVISOR_PHP_USER)s
|
||||||
|
environment=LARAVEL_SAIL="1"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
70
docker/8.2/Dockerfile
Normal file
70
docker/8.2/Dockerfile
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
LABEL maintainer="Taylor Otwell"
|
||||||
|
|
||||||
|
ARG WWWGROUP
|
||||||
|
ARG NODE_VERSION=22
|
||||||
|
ARG POSTGRES_VERSION=17
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=UTC
|
||||||
|
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
|
||||||
|
ENV SUPERVISOR_PHP_USER="sail"
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
|
||||||
|
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y php8.2-cli php8.2-dev \
|
||||||
|
php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \
|
||||||
|
php8.2-curl php8.2-mongodb \
|
||||||
|
php8.2-imap php8.2-mysql php8.2-mbstring \
|
||||||
|
php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \
|
||||||
|
php8.2-intl php8.2-readline \
|
||||||
|
php8.2-ldap \
|
||||||
|
php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \
|
||||||
|
php8.2-memcached php8.2-pcov php8.2-xdebug \
|
||||||
|
&& curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g npm \
|
||||||
|
&& npm install -g pnpm \
|
||||||
|
&& npm install -g bun \
|
||||||
|
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
|
||||||
|
&& curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y yarn \
|
||||||
|
&& apt-get install -y mysql-client \
|
||||||
|
&& apt-get install -y postgresql-client-$POSTGRES_VERSION \
|
||||||
|
&& apt-get -y autoremove \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2
|
||||||
|
|
||||||
|
RUN userdel -r ubuntu
|
||||||
|
RUN groupadd --force -g $WWWGROUP sail
|
||||||
|
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||||
|
|
||||||
|
COPY start-container /usr/local/bin/start-container
|
||||||
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY php.ini /etc/php/8.2/cli/conf.d/99-sail.ini
|
||||||
|
RUN chmod +x /usr/local/bin/start-container
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-container"]
|
5
docker/8.2/php.ini
Normal file
5
docker/8.2/php.ini
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[PHP]
|
||||||
|
post_max_size = 100M
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
variables_order = EGPCS
|
||||||
|
pcov.directory = .
|
26
docker/8.2/start-container
Normal file
26
docker/8.2/start-container
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
|
||||||
|
echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$WWWUSER" ]; then
|
||||||
|
usermod -u $WWWUSER sail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /.composer ]; then
|
||||||
|
mkdir /.composer
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod -R ugo+rw /.composer
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
|
||||||
|
exec "$@"
|
||||||
|
else
|
||||||
|
exec gosu $WWWUSER "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
fi
|
14
docker/8.2/supervisord.conf
Normal file
14
docker/8.2/supervisord.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php]
|
||||||
|
command=%(ENV_SUPERVISOR_PHP_COMMAND)s
|
||||||
|
user=%(ENV_SUPERVISOR_PHP_USER)s
|
||||||
|
environment=LARAVEL_SAIL="1"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
71
docker/8.3/Dockerfile
Normal file
71
docker/8.3/Dockerfile
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
LABEL maintainer="Taylor Otwell"
|
||||||
|
|
||||||
|
ARG WWWGROUP
|
||||||
|
ARG NODE_VERSION=22
|
||||||
|
ARG MYSQL_CLIENT="mysql-client"
|
||||||
|
ARG POSTGRES_VERSION=17
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=UTC
|
||||||
|
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
|
||||||
|
ENV SUPERVISOR_PHP_USER="sail"
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
|
||||||
|
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y php8.3-cli php8.3-dev \
|
||||||
|
php8.3-pgsql php8.3-sqlite3 php8.3-gd \
|
||||||
|
php8.3-curl php8.3-mongodb \
|
||||||
|
php8.3-imap php8.3-mysql php8.3-mbstring \
|
||||||
|
php8.3-xml php8.3-zip php8.3-bcmath php8.3-soap \
|
||||||
|
php8.3-intl php8.3-readline \
|
||||||
|
php8.3-ldap \
|
||||||
|
php8.3-msgpack php8.3-igbinary php8.3-redis \
|
||||||
|
php8.3-memcached php8.3-pcov php8.3-imagick php8.3-xdebug php8.3-swoole \
|
||||||
|
&& curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g npm \
|
||||||
|
&& npm install -g pnpm \
|
||||||
|
&& npm install -g bun \
|
||||||
|
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
|
||||||
|
&& curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y yarn \
|
||||||
|
&& apt-get install -y $MYSQL_CLIENT \
|
||||||
|
&& apt-get install -y postgresql-client-$POSTGRES_VERSION \
|
||||||
|
&& apt-get -y autoremove \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3
|
||||||
|
|
||||||
|
RUN userdel -r ubuntu
|
||||||
|
RUN groupadd --force -g $WWWGROUP sail
|
||||||
|
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||||
|
|
||||||
|
COPY start-container /usr/local/bin/start-container
|
||||||
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY php.ini /etc/php/8.3/cli/conf.d/99-sail.ini
|
||||||
|
RUN chmod +x /usr/local/bin/start-container
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-container"]
|
5
docker/8.3/php.ini
Normal file
5
docker/8.3/php.ini
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[PHP]
|
||||||
|
post_max_size = 100M
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
variables_order = EGPCS
|
||||||
|
pcov.directory = .
|
26
docker/8.3/start-container
Normal file
26
docker/8.3/start-container
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
|
||||||
|
echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$WWWUSER" ]; then
|
||||||
|
usermod -u $WWWUSER sail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /.composer ]; then
|
||||||
|
mkdir /.composer
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod -R ugo+rw /.composer
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
|
||||||
|
exec "$@"
|
||||||
|
else
|
||||||
|
exec gosu $WWWUSER "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
fi
|
14
docker/8.3/supervisord.conf
Normal file
14
docker/8.3/supervisord.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php]
|
||||||
|
command=%(ENV_SUPERVISOR_PHP_COMMAND)s
|
||||||
|
user=%(ENV_SUPERVISOR_PHP_USER)s
|
||||||
|
environment=LARAVEL_SAIL="1"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
71
docker/8.4/Dockerfile
Normal file
71
docker/8.4/Dockerfile
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
LABEL maintainer="Taylor Otwell"
|
||||||
|
|
||||||
|
ARG WWWGROUP
|
||||||
|
ARG NODE_VERSION=22
|
||||||
|
ARG MYSQL_CLIENT="mysql-client"
|
||||||
|
ARG POSTGRES_VERSION=17
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=UTC
|
||||||
|
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
|
||||||
|
ENV SUPERVISOR_PHP_USER="sail"
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
|
||||||
|
echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \
|
||||||
|
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y php8.4-cli php8.4-dev \
|
||||||
|
php8.4-pgsql php8.4-sqlite3 php8.4-gd \
|
||||||
|
php8.4-curl php8.4-mongodb \
|
||||||
|
php8.4-imap php8.4-mysql php8.4-mbstring \
|
||||||
|
php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \
|
||||||
|
php8.4-intl php8.4-readline \
|
||||||
|
php8.4-ldap \
|
||||||
|
php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \
|
||||||
|
php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \
|
||||||
|
&& curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g npm \
|
||||||
|
&& npm install -g pnpm \
|
||||||
|
&& npm install -g bun \
|
||||||
|
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
|
||||||
|
&& curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y yarn \
|
||||||
|
&& apt-get install -y $MYSQL_CLIENT \
|
||||||
|
&& apt-get install -y postgresql-client-$POSTGRES_VERSION \
|
||||||
|
&& apt-get -y autoremove \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.4
|
||||||
|
|
||||||
|
RUN userdel -r ubuntu
|
||||||
|
RUN groupadd --force -g $WWWGROUP sail
|
||||||
|
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||||
|
|
||||||
|
COPY start-container /usr/local/bin/start-container
|
||||||
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini
|
||||||
|
RUN chmod +x /usr/local/bin/start-container
|
||||||
|
|
||||||
|
EXPOSE 80/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["start-container"]
|
8
docker/8.4/php.ini
Normal file
8
docker/8.4/php.ini
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[PHP]
|
||||||
|
post_max_size = 100M
|
||||||
|
upload_max_filesize = 100M
|
||||||
|
variables_order = EGPCS
|
||||||
|
pcov.directory = .
|
||||||
|
|
||||||
|
[xdebug]
|
||||||
|
xdebug.mode = ${XDEBUG_MODE}
|
26
docker/8.4/start-container
Normal file
26
docker/8.4/start-container
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then
|
||||||
|
echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$WWWUSER" ]; then
|
||||||
|
usermod -u $WWWUSER sail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /.composer ]; then
|
||||||
|
mkdir /.composer
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod -R ugo+rw /.composer
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
|
||||||
|
exec "$@"
|
||||||
|
else
|
||||||
|
exec gosu $WWWUSER "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
fi
|
14
docker/8.4/supervisord.conf
Normal file
14
docker/8.4/supervisord.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php]
|
||||||
|
command=%(ENV_SUPERVISOR_PHP_COMMAND)s
|
||||||
|
user=%(ENV_SUPERVISOR_PHP_USER)s
|
||||||
|
environment=LARAVEL_SAIL="1"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
6
docker/mariadb/create-testing-database.sh
Normal file
6
docker/mariadb/create-testing-database.sh
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
/usr/bin/mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL
|
||||||
|
CREATE DATABASE IF NOT EXISTS testing;
|
||||||
|
GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%';
|
||||||
|
EOSQL
|
6
docker/mysql/create-testing-database.sh
Normal file
6
docker/mysql/create-testing-database.sh
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
mysql --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL
|
||||||
|
CREATE DATABASE IF NOT EXISTS testing;
|
||||||
|
GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%';
|
||||||
|
EOSQL
|
2
docker/pgsql/create-testing-database.sql
Normal file
2
docker/pgsql/create-testing-database.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SELECT 'CREATE DATABASE testing'
|
||||||
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'testing')\gexec
|
|
@ -1,51 +1,112 @@
|
||||||
<x-layout title="Categories">
|
<x-layout title="Categories">
|
||||||
<x-window>
|
<x-window x-data="{ tab: 'active' }">
|
||||||
<div class="sunken-panel">
|
@auth
|
||||||
<table class="interactive">
|
<x-tablist class="mt-3">
|
||||||
<thead>
|
<x-tablist.tab id="active">Active</x-tablist.tab>
|
||||||
<tr>
|
<x-tablist.tab id="deleted">Deleted</x-tablist.tab>
|
||||||
<th>Category</th>
|
</x-tablist>
|
||||||
<th>Actions</th>
|
@endauth
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody
|
|
||||||
x-data="{ hovered: null }"
|
|
||||||
x-on:mouseleave="hovered = null"
|
|
||||||
>
|
|
||||||
@foreach ($categories as $category)
|
|
||||||
<tr
|
|
||||||
id="{{ $category->id }}"
|
|
||||||
x-on:mouseenter="hovered = $event.target.id"
|
|
||||||
x-bind:class="{ 'highlighted': hovered === $el.id }"
|
|
||||||
>
|
|
||||||
<td>{{ $category->name }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{{ route('categories.show', $category->id) }}">
|
|
||||||
View
|
|
||||||
</a>
|
|
||||||
|
|
||||||
@auth
|
<div class="window" role="tabpanel" x-show="tab === 'active'">
|
||||||
<a
|
<div class="sunken-panel">
|
||||||
href="{{ route('categories.edit', $category->id) }}"
|
<table class="interactive">
|
||||||
class="ml-1"
|
<thead>
|
||||||
>
|
<tr>
|
||||||
Edit
|
<th>Category</th>
|
||||||
</a>
|
@auth
|
||||||
|
<th>Actions</th>
|
||||||
<a
|
@endauth
|
||||||
href="{{ route('categories.destroy', $category->id) }}"
|
|
||||||
class="ml-1"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
@endauth
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
</thead>
|
||||||
</tbody>
|
<tbody
|
||||||
</table>
|
x-data="{ hovered: null }"
|
||||||
|
x-on:mouseleave="hovered = null"
|
||||||
|
>
|
||||||
|
@foreach ($categories as $category)
|
||||||
|
<tr
|
||||||
|
id="{{ $category->id }}"
|
||||||
|
x-on:mouseenter="hovered = $event.target.id"
|
||||||
|
x-bind:class="{ 'highlighted': hovered === $el.id }"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="{{ route('categories.show', $category->id) }}"
|
||||||
|
class="text-inherit no-underline"
|
||||||
|
>
|
||||||
|
{{ $category->name }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
@auth
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="{{ route('categories.edit', $category->id) }}"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<form
|
||||||
|
action="{{ route('categories.destroy', $category->id) }}"
|
||||||
|
method="POST"
|
||||||
|
class="ml-1 inline-block"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
x-data
|
||||||
|
x-on:click.prevent="$el.closest('form').submit()"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
@endauth
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="window" role="tabpanel" x-show="tab === 'deleted'">
|
||||||
|
<div class="sunken-panel">
|
||||||
|
<table class="interactive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Category</td>
|
||||||
|
<td>Actions</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach ($trashedCategories as $category)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $category->name }}</td>
|
||||||
|
<td>
|
||||||
|
<form
|
||||||
|
action="{{ route('categories.update', $category->id) }}"
|
||||||
|
method="POST"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
@method('PATCH')
|
||||||
|
|
||||||
|
<input type="hidden" name="restore" value="1" />
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
x-on:click.prevent="$el.closest('form').submit()"
|
||||||
|
>
|
||||||
|
Restore
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@auth
|
@auth
|
||||||
<a href="{{ route('categories.create') }}" class="button">New</a>
|
<a href="{{ route('categories.create') }}" class="button">New</a>
|
||||||
@endauth
|
@endauth
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<x-layout :title="'Category: '. $category->name">
|
<x-layout :title="'Category: '. $category->name">
|
||||||
<x-window>
|
<x-window>
|
||||||
|
@foreach ($category->programs as $program)
|
||||||
|
{{ $program->name }}
|
||||||
|
@endforeach
|
||||||
|
|
||||||
<a href="{{ route('categories.index') }}">All</a>
|
<a href="{{ route('categories.index') }}">All</a>
|
||||||
</x-window>
|
</x-window>
|
||||||
</x-layout>
|
</x-layout>
|
||||||
|
|
|
@ -21,11 +21,17 @@
|
||||||
<script src="//polyfill.io/v3/polyfill.min.js?flags=gated&features=default,es5,es6,es7,matchMedia,IntersectionObserver,ResizeObserver,NodeList.prototype.forEach,HTMLTemplateElement,Element.prototype.closest,requestAnimationFrame,CustomEvent,URLSearchParams,queueMicrotask"></script>
|
<script src="//polyfill.io/v3/polyfill.min.js?flags=gated&features=default,es5,es6,es7,matchMedia,IntersectionObserver,ResizeObserver,NodeList.prototype.forEach,HTMLTemplateElement,Element.prototype.closest,requestAnimationFrame,CustomEvent,URLSearchParams,queueMicrotask"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="flex gap-6">
|
||||||
<main class="max-w-[600px]">
|
<main class="min-w-[600px]">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
|
|
||||||
<x-layout.navigation />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
@if (session('status'))
|
||||||
|
<x-window class="h-fit" title="Notification" dismissible>
|
||||||
|
{{ session('status') }}
|
||||||
|
</x-window>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-layout.navigation />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -46,10 +46,7 @@ class="window"
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
x-on:click="
|
x-on:click.prevent="$el.closest('form').submit()"
|
||||||
$event.preventDefault()
|
|
||||||
$el.closest('form').submit()
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="{{ Vite::asset('resources/images/startmenu/key.png') }}"
|
src="{{ Vite::asset('resources/images/startmenu/key.png') }}"
|
||||||
|
|
15
resources/views/components/tablist/tab.blade.php
Normal file
15
resources/views/components/tablist/tab.blade.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
@props([
|
||||||
|
'id',
|
||||||
|
'tabVarName' => 'tab',
|
||||||
|
])
|
||||||
|
|
||||||
|
<li
|
||||||
|
role="tab"
|
||||||
|
x-bind:aria-selected="{{ $tabVarName }} === $el.id"
|
||||||
|
id="{{ $id }}"
|
||||||
|
{{ $attributes }}
|
||||||
|
>
|
||||||
|
<a href="#" x-on:click.prevent="{{ $tabVarName }} = $el.parentElement.id">
|
||||||
|
{{ $slot }}
|
||||||
|
</a>
|
||||||
|
</li>
|
3
resources/views/components/tablist/tablist.blade.php
Normal file
3
resources/views/components/tablist/tablist.blade.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<menu role="tablist" {{ $attributes }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</menu>
|
|
@ -4,6 +4,7 @@
|
||||||
'restore' => true,
|
'restore' => true,
|
||||||
'help' => false,
|
'help' => false,
|
||||||
'close' => true,
|
'close' => true,
|
||||||
|
'dismissible' => false,
|
||||||
])
|
])
|
||||||
|
|
||||||
@aware([
|
@aware([
|
||||||
|
@ -14,9 +15,16 @@
|
||||||
if ($restore) {
|
if ($restore) {
|
||||||
$maximize = false;
|
$maximize = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($dismissible) {
|
||||||
|
$close = true;
|
||||||
|
}
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div {{ $attributes->merge(['class' => 'window']) }}>
|
<div
|
||||||
|
{{ $attributes->merge(['class' => 'window']) }}
|
||||||
|
{!! $dismissible ? 'x-data="{ show: true }" x-show="show"' : '' !!}
|
||||||
|
>
|
||||||
<div class="title-bar">
|
<div class="title-bar">
|
||||||
<div class="title-bar-text">{{ $title }}</div>
|
<div class="title-bar-text">{{ $title }}</div>
|
||||||
|
|
||||||
|
@ -39,7 +47,11 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($close)
|
@if ($close)
|
||||||
<button class="close" aria-hidden="true"></button>
|
<button
|
||||||
|
class="close"
|
||||||
|
aria-hidden="true"
|
||||||
|
{!! $dismissible ? 'x-on:click="show = false"' : '' !!}
|
||||||
|
></button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
0
resources/views/programs/index.blade.php
Normal file
0
resources/views/programs/index.blade.php
Normal file
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use App\Http\Controllers\CategoryController;
|
use App\Http\Controllers\CategoryController;
|
||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
|
use App\Http\Controllers\ProgramController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
|
@ -25,4 +26,5 @@
|
||||||
|
|
||||||
Route::resources([
|
Route::resources([
|
||||||
'categories' => CategoryController::class,
|
'categories' => CategoryController::class,
|
||||||
]);
|
'programs' => ProgramController::class
|
||||||
|
], ['trashed' => []]);
|
||||||
|
|
241
tests/Feature/CategoryTest.php
Normal file
241
tests/Feature/CategoryTest.php
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\CategoryController;
|
||||||
|
use App\Models\Category;
|
||||||
|
use function Pest\Laravel\delete;
|
||||||
|
use function Pest\Laravel\get;
|
||||||
|
use function Pest\Laravel\patch;
|
||||||
|
|
||||||
|
covers(CategoryController::class);
|
||||||
|
|
||||||
|
dataset('invalid-values', [null, true, 50]);
|
||||||
|
dataset('valid-values', ['hello', 'hello there', 'h#llo', 'h3llo']);
|
||||||
|
|
||||||
|
describe('categories.index', function () {
|
||||||
|
test('can be rendered to guests', function () {
|
||||||
|
get(route('categories.index'))->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be rendered to logged in users', function () {
|
||||||
|
asAdmin()->get(route('categories.index'))->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('contains categories', function () {
|
||||||
|
$response = get(route('categories.index'));
|
||||||
|
|
||||||
|
expect($response['categories'])
|
||||||
|
->toBeEloquentCollection()
|
||||||
|
->toContainOnlyInstancesOf(Category::class)
|
||||||
|
->toEqual(Category::all());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('contains trashedCategories', function () {
|
||||||
|
$response = get(route('categories.index'));
|
||||||
|
|
||||||
|
expect($response['trashedCategories'])
|
||||||
|
->toBeEloquentCollection()
|
||||||
|
->toContainOnlyInstancesOf(Category::class)
|
||||||
|
->toEqual(Category::onlyTrashed()->get());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('categories.create', function () {
|
||||||
|
test("can't be rendered to guests", function () {
|
||||||
|
get(route('categories.create'))->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be rendered to logged in users', function () {
|
||||||
|
asAdmin()->get(route('categories.create'))->assertOk();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('categories.store', function () {
|
||||||
|
test("can't be accessed by guests", function () {
|
||||||
|
$category = Category::factory()->make();
|
||||||
|
|
||||||
|
$this->post(
|
||||||
|
route('categories.store'),
|
||||||
|
['name' => $category->name]
|
||||||
|
)->assertForbidden();
|
||||||
|
expect($category)->not->toMatchDatabaseRecord();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be accessed by logged in users', function () {
|
||||||
|
$category = Category::factory()->make();
|
||||||
|
|
||||||
|
asAdmin()->post(
|
||||||
|
route('categories.store'),
|
||||||
|
['name' => $category->name]
|
||||||
|
)->assertRedirect(route('categories.index'));
|
||||||
|
expect($category)->toMatchDatabaseRecord();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stores valid categories', function (string $name) {
|
||||||
|
$category = Category::factory()->make(['name' => $name]);
|
||||||
|
|
||||||
|
asAdmin()->post(
|
||||||
|
route('categories.store'),
|
||||||
|
['name' => $name]
|
||||||
|
)->assertRedirect(route('categories.index'));
|
||||||
|
expect($category)->toMatchDatabaseRecord();
|
||||||
|
})->with('valid-values');
|
||||||
|
|
||||||
|
test('does not store invalid categories', function (mixed $name) {
|
||||||
|
$category = Category::factory()->make(['name' => $name]);
|
||||||
|
|
||||||
|
asAdmin()->post(
|
||||||
|
route('categories.store'),
|
||||||
|
['name' => $name],
|
||||||
|
)->assertRedirect();
|
||||||
|
expect($category)->not->toMatchDatabaseRecord();
|
||||||
|
})->with('invalid-values');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('categories.show', function () {
|
||||||
|
test('can be rendered to guests', function () {
|
||||||
|
get(route('categories.show', Category::factory()->create()))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be rendered to logged in users', function () {
|
||||||
|
asAdmin()
|
||||||
|
->get(route('categories.show', Category::factory()->create()))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('contains the category', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
$response = get(route('categories.show', $category));
|
||||||
|
|
||||||
|
expect($response['category'])
|
||||||
|
->id->toBe($category->id)
|
||||||
|
->name->toBe($category->name);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can show trashed categories', function () {
|
||||||
|
$category = Category::factory()->trashed()->create();
|
||||||
|
|
||||||
|
$response = get(route('categories.show', $category));
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
expect($response['category'])
|
||||||
|
->id->toBe($category->id)
|
||||||
|
->name->toBe($category->name)
|
||||||
|
->and($category)->toBeSoftDeleted();
|
||||||
|
// @formatter:on
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('categories.edit', function () {
|
||||||
|
test("can't be rendered to guests", function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
get(route('categories.edit', $category))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be rendered to logged in users', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
asAdmin()
|
||||||
|
->get(route('categories.edit', $category))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('contains the category', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
$response = asAdmin()->get(route('categories.edit', $category));
|
||||||
|
|
||||||
|
expect($response['category'])
|
||||||
|
->id->toBe($category->id)
|
||||||
|
->name->toBe($category->name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('categories.update', function () {
|
||||||
|
test("can't be accessed by guests", function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
$updated = Category::factory()->make();
|
||||||
|
|
||||||
|
patch(
|
||||||
|
route('categories.update', $category),
|
||||||
|
['name' => $updated->name]
|
||||||
|
)->assertForbidden();
|
||||||
|
expect($category)->toBeInDatabaseExactly();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be accessed by logged in users', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
$updated = Category::factory()->make();
|
||||||
|
|
||||||
|
asAdmin()->patch(
|
||||||
|
route('categories.update', $category),
|
||||||
|
['name' => $updated->name]
|
||||||
|
)->assertRedirect(route('categories.index', absolute: false));
|
||||||
|
|
||||||
|
$category->refresh();
|
||||||
|
|
||||||
|
expect($category)->toMatchObject($updated);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates categories with valid values', function (string $name) {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
asAdmin()->patch(
|
||||||
|
route('categories.update', $category),
|
||||||
|
['name' => $name]
|
||||||
|
)->assertRedirect(route('categories.index', absolute: false));
|
||||||
|
|
||||||
|
$category->refresh();
|
||||||
|
|
||||||
|
expect($category->name)->toEqual($name);
|
||||||
|
})->with('valid-values');
|
||||||
|
|
||||||
|
test(
|
||||||
|
'does not update categories with invalid values',
|
||||||
|
function (mixed $name) {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
asAdmin()->patch(
|
||||||
|
route('categories.update', $category),
|
||||||
|
['name' => $name]
|
||||||
|
)->assertRedirect();
|
||||||
|
|
||||||
|
$updated = Category::find($category->id);
|
||||||
|
expect($updated->name)->toEqual($category->name);
|
||||||
|
}
|
||||||
|
)->with('invalid-values');
|
||||||
|
|
||||||
|
test('restores trashed categories', function () {
|
||||||
|
$category = Category::factory()->trashed()->create();
|
||||||
|
|
||||||
|
asAdmin()
|
||||||
|
->patch(route('categories.update', $category), [
|
||||||
|
'restore' => 1
|
||||||
|
])->assertRedirect();
|
||||||
|
|
||||||
|
$category->refresh();
|
||||||
|
expect($category)->not->toBeSoftDeleted();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('categories.destroy', function () {
|
||||||
|
test("can't be accessed by guests", function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
delete(route('categories.destroy', $category))
|
||||||
|
->assertForbidden();
|
||||||
|
expect($category)->not->toBeSoftDeleted();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be accessed by logged in users', function () {
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
asAdmin()
|
||||||
|
->delete(route('categories.destroy', $category))
|
||||||
|
->assertRedirect(route('categories.index', absolute: false));
|
||||||
|
expect($category)->toBeSoftDeleted();
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,10 +11,20 @@
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pest()->extend(Tests\TestCase::class)
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Pest\Expectation;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use function Pest\Laravel\assertDatabaseHas;
|
||||||
|
use function Pest\Laravel\assertModelExists;
|
||||||
|
|
||||||
|
pest()
|
||||||
|
->extend(Tests\TestCase::class)
|
||||||
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
|
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
|
||||||
->in('Feature');
|
->in('Feature');
|
||||||
|
|
||||||
|
arch()->preset()->laravel();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Expectations
|
| Expectations
|
||||||
|
@ -26,9 +36,42 @@
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
expect()->extend('toBeOne', function () {
|
// https://github.com/defstudio/pest-plugin-laravel-expectations
|
||||||
return $this->toBe(1);
|
|
||||||
});
|
expect()->extend(
|
||||||
|
'toMatchDatabaseRecord',
|
||||||
|
function (?string $table = null, ?string $connection = null): Expectation {
|
||||||
|
$this->toBeInstanceOf(Model::class);
|
||||||
|
|
||||||
|
$table = $table ?? $this->value->getTable();
|
||||||
|
$value = $this->value->attributesToArray();
|
||||||
|
|
||||||
|
assertDatabaseHas($table, $value, $connection);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect()->extend(
|
||||||
|
'toBeInDatabaseExactly',
|
||||||
|
function (?string $table = null, ?string $connection = null): Expectation {
|
||||||
|
assertModelExists($this->value);
|
||||||
|
|
||||||
|
return $this->toMatchDatabaseRecord();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect()->pipe(
|
||||||
|
'toMatchObject',
|
||||||
|
function (Closure $next, mixed $expected) {
|
||||||
|
if ($expected instanceof Model) {
|
||||||
|
return expect($this->value)
|
||||||
|
->toMatchObject($expected->attributesToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -41,7 +84,7 @@
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function something()
|
function asAdmin(): TestCase
|
||||||
{
|
{
|
||||||
// ..
|
return test()->actingAs(User::factory()->create());
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,11 @@
|
||||||
|
|
||||||
abstract class TestCase extends BaseTestCase
|
abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
//
|
/**
|
||||||
|
* Indicates whether the default seeder should run before each test.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @noinspection PhpMissingFieldTypeInspection
|
||||||
|
*/
|
||||||
|
// protected $seed = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue