user profiles
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the specified user's profile.
|
||||||
|
*/
|
||||||
|
public function show(User $user)
|
||||||
|
{
|
||||||
|
return view('profile.show', compact('user'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the authenticated user's profile.
|
||||||
|
*/
|
||||||
|
public function edit()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
return view('profile.edit', compact('user'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the authenticated user's profile.
|
||||||
|
*/
|
||||||
|
public function update(Request $request)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
|
||||||
|
'avatar' => ['nullable', 'image', 'mimes:jpeg,png,jpg,gif', 'max:2048'],
|
||||||
|
'bio' => ['nullable', 'string', 'max:1000'],
|
||||||
|
'website' => ['nullable', 'url', 'max:255'],
|
||||||
|
'twitter' => ['nullable', 'string', 'max:255'],
|
||||||
|
'github' => ['nullable', 'string', 'max:255'],
|
||||||
|
'linkedin' => ['nullable', 'string', 'max:255'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Handle avatar upload
|
||||||
|
if ($request->hasFile('avatar')) {
|
||||||
|
// Delete old avatar if exists
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete('avatars/' . $user->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store new avatar
|
||||||
|
$avatarName = time() . '_' . $user->id . '.' . $request->avatar->extension();
|
||||||
|
$request->avatar->storeAs('avatars', $avatarName, 'public');
|
||||||
|
$validated['avatar'] = $avatarName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user profile
|
||||||
|
$user->update($validated);
|
||||||
|
|
||||||
|
return redirect()->route('profile.show', $user)
|
||||||
|
->with('success', 'Profile updated successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the user's avatar.
|
||||||
|
*/
|
||||||
|
public function deleteAvatar()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete('avatars/' . $user->avatar);
|
||||||
|
$user->update(['avatar' => null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('profile.edit')
|
||||||
|
->with('success', 'Avatar deleted successfully!');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IsAdmin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!auth()->check() || !auth()->user()->isAdmin()) {
|
||||||
|
abort(403, 'Unauthorized action. Admin access required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IsModerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!auth()->check() || !auth()->user()->hasAnyRole(['moderator', 'admin'])) {
|
||||||
|
abort(403, 'Unauthorized action. Moderator access required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
|
class Role extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'slug',
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the users that belong to the role.
|
||||||
|
*/
|
||||||
|
public function users(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
@@ -21,6 +22,12 @@ class User extends Authenticatable
|
|||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'avatar',
|
||||||
|
'bio',
|
||||||
|
'website',
|
||||||
|
'twitter',
|
||||||
|
'github',
|
||||||
|
'linkedin',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,4 +52,63 @@ class User extends Authenticatable
|
|||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the roles that belong to the user.
|
||||||
|
*/
|
||||||
|
public function roles(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Role::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has a specific role.
|
||||||
|
*/
|
||||||
|
public function hasRole(string $role): bool
|
||||||
|
{
|
||||||
|
return $this->roles()->where('slug', $role)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has any of the given roles.
|
||||||
|
*/
|
||||||
|
public function hasAnyRole(array $roles): bool
|
||||||
|
{
|
||||||
|
return $this->roles()->whereIn('slug', $roles)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is an admin.
|
||||||
|
*/
|
||||||
|
public function isAdmin(): bool
|
||||||
|
{
|
||||||
|
return $this->hasRole('admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is a moderator.
|
||||||
|
*/
|
||||||
|
public function isModerator(): bool
|
||||||
|
{
|
||||||
|
return $this->hasRole('moderator');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is a regular user.
|
||||||
|
*/
|
||||||
|
public function isUser(): bool
|
||||||
|
{
|
||||||
|
return $this->hasRole('user');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user's avatar URL.
|
||||||
|
*/
|
||||||
|
public function getAvatarUrlAttribute(): ?string
|
||||||
|
{
|
||||||
|
if ($this->avatar) {
|
||||||
|
return asset('storage/avatars/' . $this->avatar);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -19,6 +20,50 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
// Product-related Gates
|
||||||
|
Gate::define('create-product', function ($user) {
|
||||||
|
return $user->hasAnyRole(['user', 'moderator', 'admin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Gate::define('edit-product', function ($user, $product) {
|
||||||
|
return $user->id === $product->user_id || $user->hasAnyRole(['moderator', 'admin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Gate::define('delete-product', function ($user, $product) {
|
||||||
|
return $user->id === $product->user_id || $user->hasAnyRole(['moderator', 'admin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Gate::define('approve-product', function ($user) {
|
||||||
|
return $user->hasAnyRole(['moderator', 'admin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Gate::define('reject-product', function ($user) {
|
||||||
|
return $user->hasAnyRole(['moderator', 'admin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Comment-related Gates
|
||||||
|
Gate::define('delete-comment', function ($user, $comment) {
|
||||||
|
return $user->id === $comment->user_id || $user->hasAnyRole(['moderator', 'admin']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// User management Gates
|
||||||
|
Gate::define('manage-users', function ($user) {
|
||||||
|
return $user->isAdmin();
|
||||||
|
});
|
||||||
|
|
||||||
|
Gate::define('manage-roles', function ($user) {
|
||||||
|
return $user->isAdmin();
|
||||||
|
});
|
||||||
|
|
||||||
|
Gate::define('manage-settings', function ($user) {
|
||||||
|
return $user->isAdmin();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin check
|
||||||
|
Gate::before(function ($user, $ability) {
|
||||||
|
if ($user->isAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -11,7 +11,10 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
$middleware->alias([
|
||||||
|
'admin' => \App\Http\Middleware\IsAdmin::class,
|
||||||
|
'moderator' => \App\Http\Middleware\IsModerator::class,
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('avatar')->nullable()->after('email');
|
||||||
|
$table->text('bio')->nullable()->after('avatar');
|
||||||
|
$table->string('website')->nullable()->after('bio');
|
||||||
|
$table->string('twitter')->nullable()->after('website');
|
||||||
|
$table->string('github')->nullable()->after('twitter');
|
||||||
|
$table->string('linkedin')->nullable()->after('github');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['avatar', 'bio', 'website', 'twitter', 'github', 'linkedin']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('roles', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->string('slug')->unique();
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('roles');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('role_user', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->foreignId('role_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['user_id', 'role_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('role_user');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -15,11 +15,11 @@ class DatabaseSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
$this->call([
|
||||||
|
RoleSeeder::class,
|
||||||
User::factory()->create([
|
|
||||||
'name' => 'Test User',
|
|
||||||
'email' => 'test@example.com',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Uncomment to create additional users with factory
|
||||||
|
// User::factory(10)->create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Role;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class RoleSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// Create roles
|
||||||
|
$adminRole = Role::create([
|
||||||
|
'name' => 'Admin',
|
||||||
|
'slug' => 'admin',
|
||||||
|
'description' => 'Full access to all features and settings',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$moderatorRole = Role::create([
|
||||||
|
'name' => 'Moderator',
|
||||||
|
'slug' => 'moderator',
|
||||||
|
'description' => 'Can approve/reject products and delete comments',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$userRole = Role::create([
|
||||||
|
'name' => 'User',
|
||||||
|
'slug' => 'user',
|
||||||
|
'description' => 'Can submit products, upvote, and comment',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create test users
|
||||||
|
$admin = User::create([
|
||||||
|
'name' => 'Admin User',
|
||||||
|
'email' => 'admin@example.com',
|
||||||
|
'password' => Hash::make('password'),
|
||||||
|
'bio' => 'Administrator of the platform',
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]);
|
||||||
|
$admin->roles()->attach($adminRole);
|
||||||
|
|
||||||
|
$moderator = User::create([
|
||||||
|
'name' => 'Moderator User',
|
||||||
|
'email' => 'moderator@example.com',
|
||||||
|
'password' => Hash::make('password'),
|
||||||
|
'bio' => 'Content moderator',
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]);
|
||||||
|
$moderator->roles()->attach($moderatorRole);
|
||||||
|
|
||||||
|
$regularUser = User::create([
|
||||||
|
'name' => 'Regular User',
|
||||||
|
'email' => 'user@example.com',
|
||||||
|
'password' => Hash::make('password'),
|
||||||
|
'bio' => 'Regular platform user',
|
||||||
|
'website' => 'https://example.com',
|
||||||
|
'twitter' => 'example',
|
||||||
|
'github' => 'example',
|
||||||
|
'linkedin' => 'example',
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]);
|
||||||
|
$regularUser->roles()->attach($userRole);
|
||||||
|
|
||||||
|
$this->command->info('Roles and test users created successfully!');
|
||||||
|
$this->command->info('Admin: admin@example.com / password');
|
||||||
|
$this->command->info('Moderator: moderator@example.com / password');
|
||||||
|
$this->command->info('User: user@example.com / password');
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+2856
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Edit Profile</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
@if (session('success'))
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
{{ session('success') }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('profile.update') }}" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<!-- Avatar Section -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label">Profile Picture</label>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="me-3">
|
||||||
|
@if ($user->avatar)
|
||||||
|
<img src="{{ $user->avatar_url }}" alt="{{ $user->name }}" class="rounded-circle" style="width: 100px; height: 100px; object-fit: cover;">
|
||||||
|
@else
|
||||||
|
<div class="bg-secondary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 100px; height: 100px; font-size: 36px;">
|
||||||
|
{{ strtoupper(substr($user->name, 0, 1)) }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<input type="file" class="form-control @error('avatar') is-invalid @enderror" id="avatar" name="avatar" accept="image/*">
|
||||||
|
@error('avatar')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
<small class="form-text text-muted">JPG, PNG, or GIF. Max size 2MB.</small>
|
||||||
|
@if ($user->avatar)
|
||||||
|
<div class="mt-2">
|
||||||
|
<form method="POST" action="{{ route('profile.avatar.delete') }}" class="d-inline">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete your avatar?')">
|
||||||
|
Delete Avatar
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name', $user->name) }}" required>
|
||||||
|
@error('name')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control @error('email') is-invalid @enderror" id="email" name="email" value="{{ old('email', $user->email) }}" required>
|
||||||
|
@error('email')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bio -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="bio" class="form-label">Bio</label>
|
||||||
|
<textarea class="form-control @error('bio') is-invalid @enderror" id="bio" name="bio" rows="4" maxlength="1000">{{ old('bio', $user->bio) }}</textarea>
|
||||||
|
@error('bio')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
<small class="form-text text-muted">Max 1000 characters</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Website -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="website" class="form-label">Website</label>
|
||||||
|
<input type="url" class="form-control @error('website') is-invalid @enderror" id="website" name="website" value="{{ old('website', $user->website) }}" placeholder="https://example.com">
|
||||||
|
@error('website')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Links -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="twitter" class="form-label">Twitter Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">@</span>
|
||||||
|
<input type="text" class="form-control @error('twitter') is-invalid @enderror" id="twitter" name="twitter" value="{{ old('twitter', $user->twitter) }}" placeholder="username">
|
||||||
|
@error('twitter')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="github" class="form-label">GitHub Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">github.com/</span>
|
||||||
|
<input type="text" class="form-control @error('github') is-invalid @enderror" id="github" name="github" value="{{ old('github', $user->github) }}" placeholder="username">
|
||||||
|
@error('github')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="linkedin" class="form-label">LinkedIn Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">linkedin.com/in/</span>
|
||||||
|
<input type="text" class="form-control @error('linkedin') is-invalid @enderror" id="linkedin" name="linkedin" value="{{ old('linkedin', $user->linkedin) }}" placeholder="username">
|
||||||
|
@error('linkedin')
|
||||||
|
<span class="invalid-feedback" role="alert">
|
||||||
|
<strong>{{ $message }}</strong>
|
||||||
|
</span>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<a href="{{ route('profile.show', $user) }}" class="btn btn-secondary">Cancel</a>
|
||||||
|
<button type="submit" class="btn btn-primary">Update Profile</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<span>{{ $user->name }}'s Profile</span>
|
||||||
|
@auth
|
||||||
|
@if (Auth::id() === $user->id)
|
||||||
|
<a href="{{ route('profile.edit') }}" class="btn btn-sm btn-primary">Edit Profile</a>
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 text-center">
|
||||||
|
@if ($user->avatar)
|
||||||
|
<img src="{{ $user->avatar_url }}" alt="{{ $user->name }}" class="img-fluid rounded-circle mb-3" style="max-width: 150px;">
|
||||||
|
@else
|
||||||
|
<div class="bg-secondary text-white rounded-circle d-flex align-items-center justify-content-center mb-3" style="width: 150px; height: 150px; font-size: 48px; margin: 0 auto;">
|
||||||
|
{{ strtoupper(substr($user->name, 0, 1)) }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<h3>{{ $user->name }}</h3>
|
||||||
|
<p class="text-muted">{{ $user->email }}</p>
|
||||||
|
|
||||||
|
@if ($user->bio)
|
||||||
|
<div class="mt-3">
|
||||||
|
<h5>Bio</h5>
|
||||||
|
<p>{{ $user->bio }}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($user->website || $user->twitter || $user->github || $user->linkedin)
|
||||||
|
<div class="mt-4">
|
||||||
|
<h5>Links</h5>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
@if ($user->website)
|
||||||
|
<li class="mb-2">
|
||||||
|
<strong>Website:</strong>
|
||||||
|
<a href="{{ $user->website }}" target="_blank" rel="noopener">{{ $user->website }}</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
@if ($user->twitter)
|
||||||
|
<li class="mb-2">
|
||||||
|
<strong>Twitter:</strong>
|
||||||
|
<a href="https://twitter.com/{{ $user->twitter }}" target="_blank" rel="noopener">@{{ $user->twitter }}</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
@if ($user->github)
|
||||||
|
<li class="mb-2">
|
||||||
|
<strong>GitHub:</strong>
|
||||||
|
<a href="https://github.com/{{ $user->github }}" target="_blank" rel="noopener">{{ $user->github }}</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
@if ($user->linkedin)
|
||||||
|
<li class="mb-2">
|
||||||
|
<strong>LinkedIn:</strong>
|
||||||
|
<a href="https://linkedin.com/in/{{ $user->linkedin }}" target="_blank" rel="noopener">{{ $user->linkedin }}</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($user->roles->isNotEmpty())
|
||||||
|
<div class="mt-4">
|
||||||
|
<h5>Roles</h5>
|
||||||
|
<div>
|
||||||
|
@foreach ($user->roles as $role)
|
||||||
|
<span class="badge bg-primary me-1">{{ $role->name }}</span>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="mt-3 text-muted">
|
||||||
|
<small>Member since {{ $user->created_at->format('F Y') }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\ProfileController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
@@ -9,3 +10,38 @@ Route::get('/', function () {
|
|||||||
Auth::routes();
|
Auth::routes();
|
||||||
|
|
||||||
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
|
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
|
||||||
|
|
||||||
|
// Profile Routes
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::get('/profile/edit', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||||
|
Route::put('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||||
|
Route::delete('/profile/avatar', [ProfileController::class, 'deleteAvatar'])->name('profile.avatar.delete');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/profile/{user}', [ProfileController::class, 'show'])->name('profile.show');
|
||||||
|
|
||||||
|
// Admin Routes (example)
|
||||||
|
Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () {
|
||||||
|
Route::get('/dashboard', function () {
|
||||||
|
return 'Admin Dashboard';
|
||||||
|
})->name('dashboard');
|
||||||
|
|
||||||
|
Route::get('/users', function () {
|
||||||
|
return 'User Management (Admin Only)';
|
||||||
|
})->name('users.index');
|
||||||
|
|
||||||
|
Route::get('/settings', function () {
|
||||||
|
return 'Settings (Admin Only)';
|
||||||
|
})->name('settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Moderator Routes (example)
|
||||||
|
Route::middleware(['auth', 'moderator'])->prefix('moderator')->name('moderator.')->group(function () {
|
||||||
|
Route::get('/dashboard', function () {
|
||||||
|
return 'Moderator Dashboard';
|
||||||
|
})->name('dashboard');
|
||||||
|
|
||||||
|
Route::get('/products/pending', function () {
|
||||||
|
return 'Pending Products (Moderator Only)';
|
||||||
|
})->name('products.pending');
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Role;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('users can view a profile', function () {
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'name' => 'John Doe',
|
||||||
|
'bio' => 'Test bio',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->get(route('profile.show', $user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('John Doe');
|
||||||
|
$response->assertSee('Test bio');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('authenticated users can view their edit profile page', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get(route('profile.edit'));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('Edit Profile');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guests cannot view edit profile page', function () {
|
||||||
|
$response = $this->get(route('profile.edit'));
|
||||||
|
|
||||||
|
$response->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('users can update their profile', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->put(route('profile.update'), [
|
||||||
|
'name' => 'Updated Name',
|
||||||
|
'email' => $user->email,
|
||||||
|
'bio' => 'Updated bio',
|
||||||
|
'website' => 'https://example.com',
|
||||||
|
'twitter' => 'twitter_handle',
|
||||||
|
'github' => 'github_handle',
|
||||||
|
'linkedin' => 'linkedin_handle',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertRedirect(route('profile.show', $user));
|
||||||
|
$response->assertSessionHas('success');
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
expect($user->name)->toBe('Updated Name');
|
||||||
|
expect($user->bio)->toBe('Updated bio');
|
||||||
|
expect($user->website)->toBe('https://example.com');
|
||||||
|
expect($user->twitter)->toBe('twitter_handle');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('users can upload an avatar', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$file = UploadedFile::fake()->image('avatar.jpg');
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->put(route('profile.update'), [
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => $user->email,
|
||||||
|
'avatar' => $file,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertRedirect(route('profile.show', $user));
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
expect($user->avatar)->not->toBeNull();
|
||||||
|
Storage::disk('public')->assertExists('avatars/' . $user->avatar);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('avatar must be an image', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$file = UploadedFile::fake()->create('document.pdf');
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->put(route('profile.update'), [
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => $user->email,
|
||||||
|
'avatar' => $file,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertSessionHasErrors('avatar');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('avatar cannot exceed 2MB', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$file = UploadedFile::fake()->image('avatar.jpg')->size(3000);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->put(route('profile.update'), [
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => $user->email,
|
||||||
|
'avatar' => $file,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertSessionHasErrors('avatar');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('users can delete their avatar', function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
$user = User::factory()->create(['avatar' => 'test_avatar.jpg']);
|
||||||
|
Storage::disk('public')->put('avatars/test_avatar.jpg', 'fake content');
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->delete(route('profile.avatar.delete'));
|
||||||
|
|
||||||
|
$response->assertRedirect(route('profile.edit'));
|
||||||
|
$response->assertSessionHas('success');
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
expect($user->avatar)->toBeNull();
|
||||||
|
Storage::disk('public')->assertMissing('avatars/test_avatar.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('email must be unique when updating profile', function () {
|
||||||
|
$existingUser = User::factory()->create(['email' => 'existing@example.com']);
|
||||||
|
$user = User::factory()->create(['email' => 'user@example.com']);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->put(route('profile.update'), [
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => 'existing@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertSessionHasErrors('email');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('website must be a valid url', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->put(route('profile.update'), [
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => $user->email,
|
||||||
|
'website' => 'not-a-valid-url',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertSessionHasErrors('website');
|
||||||
|
});
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Role;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Create roles
|
||||||
|
$this->adminRole = Role::create([
|
||||||
|
'name' => 'Admin',
|
||||||
|
'slug' => 'admin',
|
||||||
|
'description' => 'Full access',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->moderatorRole = Role::create([
|
||||||
|
'name' => 'Moderator',
|
||||||
|
'slug' => 'moderator',
|
||||||
|
'description' => 'Moderate content',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->userRole = Role::create([
|
||||||
|
'name' => 'User',
|
||||||
|
'slug' => 'user',
|
||||||
|
'description' => 'Regular user',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('users can be assigned roles', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect($user->roles)->toHaveCount(1);
|
||||||
|
expect($user->roles->first()->slug)->toBe('user');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can have multiple roles', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach([$this->userRole->id, $this->moderatorRole->id]);
|
||||||
|
|
||||||
|
expect($user->roles)->toHaveCount(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasRole method works correctly', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect($user->hasRole('user'))->toBeTrue();
|
||||||
|
expect($user->hasRole('admin'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasAnyRole method works correctly', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->moderatorRole);
|
||||||
|
|
||||||
|
expect($user->hasAnyRole(['moderator', 'admin']))->toBeTrue();
|
||||||
|
expect($user->hasAnyRole(['user', 'admin']))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isAdmin method works correctly', function () {
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect($admin->isAdmin())->toBeTrue();
|
||||||
|
expect($user->isAdmin())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isModerator method works correctly', function () {
|
||||||
|
$moderator = User::factory()->create();
|
||||||
|
$moderator->roles()->attach($this->moderatorRole);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect($moderator->isModerator())->toBeTrue();
|
||||||
|
expect($user->isModerator())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin middleware blocks non-admin users', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/admin/dashboard');
|
||||||
|
|
||||||
|
$response->assertStatus(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin middleware allows admin users', function () {
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
$response = $this->actingAs($admin)->get('/admin/dashboard');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('moderator middleware blocks regular users', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/moderator/dashboard');
|
||||||
|
|
||||||
|
$response->assertStatus(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('moderator middleware allows moderators', function () {
|
||||||
|
$moderator = User::factory()->create();
|
||||||
|
$moderator->roles()->attach($this->moderatorRole);
|
||||||
|
|
||||||
|
$response = $this->actingAs($moderator)->get('/moderator/dashboard');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('moderator middleware allows admins', function () {
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
$response = $this->actingAs($admin)->get('/moderator/dashboard');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guests cannot access protected routes', function () {
|
||||||
|
$response = $this->get('/admin/dashboard');
|
||||||
|
$response->assertStatus(302);
|
||||||
|
|
||||||
|
$response = $this->get('/moderator/dashboard');
|
||||||
|
$response->assertStatus(302);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create product gate allows users moderators and admins', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($user)->allows('create-product'))->toBeTrue();
|
||||||
|
|
||||||
|
$moderator = User::factory()->create();
|
||||||
|
$moderator->roles()->attach($this->moderatorRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($moderator)->allows('create-product'))->toBeTrue();
|
||||||
|
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($admin)->allows('create-product'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('approve product gate allows only moderators and admins', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($user)->allows('approve-product'))->toBeFalse();
|
||||||
|
|
||||||
|
$moderator = User::factory()->create();
|
||||||
|
$moderator->roles()->attach($this->moderatorRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($moderator)->allows('approve-product'))->toBeTrue();
|
||||||
|
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($admin)->allows('approve-product'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manage users gate allows only admins', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->roles()->attach($this->userRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($user)->allows('manage-users'))->toBeFalse();
|
||||||
|
|
||||||
|
$moderator = User::factory()->create();
|
||||||
|
$moderator->roles()->attach($this->moderatorRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($moderator)->allows('manage-users'))->toBeFalse();
|
||||||
|
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($admin)->allows('manage-users'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin has access to all gates via gate before', function () {
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$admin->roles()->attach($this->adminRole);
|
||||||
|
|
||||||
|
expect(Gate::forUser($admin)->allows('create-product'))->toBeTrue();
|
||||||
|
expect(Gate::forUser($admin)->allows('approve-product'))->toBeTrue();
|
||||||
|
expect(Gate::forUser($admin)->allows('manage-users'))->toBeTrue();
|
||||||
|
expect(Gate::forUser($admin)->allows('manage-roles'))->toBeTrue();
|
||||||
|
expect(Gate::forUser($admin)->allows('manage-settings'))->toBeTrue();
|
||||||
|
});
|
||||||
+6
-1
@@ -6,5 +6,10 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
|||||||
|
|
||||||
abstract class TestCase extends BaseTestCase
|
abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
//
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->withoutVite();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user