first init

This commit is contained in:
ISMAIL MASSERAN
2026-06-08 11:37:14 +08:00
commit 94ecbe5887
1058 changed files with 87732 additions and 0 deletions
View File
View File
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
'name' => 'User',
];
View File
@@ -0,0 +1,16 @@
<?php
namespace Modules\User\Database\Seeders;
use Illuminate\Database\Seeder;
class UserDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// $this->call([]);
}
}
View File
View File
View File
@@ -0,0 +1,210 @@
<?php
namespace Modules\User\Http\Controllers;
use App\Http\Controllers\BaseCrudController;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Modules\Auth\Entities\User;
use Modules\User\Http\Requests\UserRequest;
use Modules\User\Repositories\Contracts\UserRepositoryInterface;
use Modules\User\Services\UserService;
use Modules\User\Transformers\UserResource;
use Modules\User\Transformers\UserListResource;
class UserController extends BaseCrudController
{
protected $modelClass = User::class;
protected $resourceClass = UserResource::class;
protected $requestClass = UserRequest::class;
protected $resourceName = 'user';
protected $resourceNamePlural = 'users';
public function __construct(
UserRepositoryInterface $repository,
protected UserService $userService
) {
parent::__construct($repository);
}
/**
* Prepare data for store method
*/
protected function prepareStoreData(array $validated): array
{
$validated['uuid'] = Str::uuid();
$validated['password'] = Hash::make('suteraselamanya');
return $validated;
}
/**
* Prepare data for update method
*/
protected function prepareUpdateData(array $validated, $user): array
{
unset($validated['uuid']);
if (! array_key_exists('email', $validated)) {
unset($validated['email']);
}
return $validated;
}
/**
* Check dependencies before deletion
*/
protected function checkDependencies($user): ?JsonResponse
{
return null;
}
public function assignRoles(Request $request, $id): JsonResponse
{
$request->validate([
'roles' => 'required|array',
'roles.*' => 'exists:roles,id,guard_name,api',
]);
$result = $this->userService->assignRoles($id, $request->roles);
if (isset($result['error'])) {
$status = $result['error'] === 'User not found.' ? 404 : 400;
return response()->json([
'success' => false,
'message' => $result['error'],
], $status);
}
return response()->json([
'success' => true,
'message' => 'Roles assigned successfully.',
'data' => (new UserResource($result['user']))->resolve(),
]);
}
public function updateProfile(Request $request): JsonResponse
{
$user = $request->user();
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'ic_number' => 'sometimes|string|max:255|unique:users,ic_number,'.$user->id,
'position' => 'sometimes|string|max:255',
'phone_number' => 'sometimes|string|max:255',
'image' => 'sometimes|image|mimes:jpeg,png,jpg,gif|max:2048',
'image_url' => 'sometimes|string|max:255',
]);
$user = $this->userService->updateProfile(
$user,
$validated,
$request->file('image')
);
return response()->json([
'success' => true,
'data' => new UserResource($user),
'message' => 'Profile updated successfully.',
]);
}
public function updatePassword(Request $request): JsonResponse
{
$user = $request->user();
$validated = $request->validate([
'current_password' => 'required|string',
'password' => 'required|string|min:8|confirmed',
]);
$result = $this->userService->updatePassword(
$user,
$validated['current_password'],
$validated['password']
);
if (isset($result['error'])) {
return response()->json([
'success' => false,
'message' => $result['error'],
], 400);
}
return response()->json([
'success' => true,
'message' => 'Password updated successfully.',
]);
}
public function index(Request $request): JsonResponse
{
$this->authorize('viewAny', $this->modelClass);
try {
$perPage = min((int) $request->get('per_page', 10), 500);
$items = $this->userService->getPaginatedList(
$perPage,
$request->get('search', ''),
$request->get('status', ''),
$request->get('sort_by', 'id'),
$request->get('sort_order', 'asc')
);
return response()->json([
'success' => true,
'data' => UserListResource::collection($items->items()),
'pagination' => [
'current_page' => $items->currentPage(),
'per_page' => $items->perPage(),
'total' => $items->total(),
'last_page' => $items->lastPage(),
'from' => $items->firstItem(),
'to' => $items->lastItem(),
'has_more_pages' => $items->hasMorePages(),
],
'message' => $this->getSuccessMessage('index'),
]);
} catch (Exception $e) {
Log::error("Error fetching {$this->resourceNamePlural}: ".$e->getMessage());
return $this->errorResponse($this->getErrorMessage('index'), 500);
}
}
public function show($id): JsonResponse
{
$this->authorize('view', $this->modelClass);
try {
$user = $this->userService->getUserWithRelations($id);
if (! $user) {
return response()->json([
'success' => false,
'message' => 'User not found.',
], 404);
}
return response()->json([
'success' => true,
'data' => new UserResource($user),
'message' => 'User retrieved successfully.',
]);
} catch (Exception $e) {
Log::error('Error fetching user: '.$e->getMessage());
return $this->errorResponse('Failed to retrieve user.', 500);
}
}
}
@@ -0,0 +1,110 @@
<?php
namespace Modules\User\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
// Get the user ID from the route (with null check)
$userId = $this->route() ? $this->route('user') : null;
// Check if this is a create or update operation
$isCreate = $this->isMethod('POST');
$isUpdate = $this->isMethod('PUT') || $this->isMethod('PATCH');
// If this is not a POST, PUT, or PATCH request, return minimal rules
if (! $isCreate && ! $isUpdate) {
return [
'name' => 'nullable|string|max:255',
'email' => 'nullable|string|email|max:255',
'password' => 'nullable|string|min:8',
'ic_number' => 'nullable|string|max:255',
'position' => 'nullable|string|max:255',
'phone_number' => 'nullable|string|max:255',
'image_url' => 'nullable|string|max:255',
'status' => 'nullable|string',
];
}
// Build unique email validation rule
$emailUniqueRule = $userId ? 'unique:users,email,'.$userId : 'unique:users,email';
// If this is an update operation, make email and password optional
if ($isUpdate) {
return [
'name' => 'required|string|max:255',
// 'email' => [
// 'nullable',
// 'string',
// 'email',
// 'max:255',
// $emailUniqueRule,
// ],
'password' => 'nullable|string|min:8',
'ic_number' => 'required|string|max:255',
'position' => 'required|string|max:255',
'phone_number' => 'nullable|string|max:255',
'image_url' => 'nullable|string|max:255',
'status' => 'nullable|string',
];
}
// Default rules for create operations
return [
'name' => 'required|string|max:255',
'email' => [
'required',
'string',
'email',
'max:255',
$emailUniqueRule,
],
'password' => 'nullable|string|min:8',
'ic_number' => 'required|string|max:255',
'position' => 'required|string|max:255',
'phone_number' => 'nullable|string|max:255',
'image_url' => 'nullable|string|max:255',
'status' => 'required|string',
];
}
/**
* Get custom messages for validator errors.
*/
public function messages(): array
{
return [
'name.required' => 'Nama diperlukan.',
'name.max' => 'Nama tidak boleh melebihi 255 aksara.',
'email.required' => 'Email diperlukan.',
'email.email' => 'Email tidak sah.',
'email.max' => 'Email tidak boleh melebihi 255 aksara.',
'email.unique' => 'Email sudah wujud.',
'password.nullable' => 'Kata laluan diperlukan.',
'ic_number.required' => 'Nombor Kad Pengenalan diperlukan.',
'ic_number.max' => 'Nombor Kad Pengenalan tidak boleh melebihi 255 aksara.',
'position.required' => 'Posisi diperlukan.',
'position.max' => 'Posisi tidak boleh melebihi 255 aksara.',
'phone_number.max' => 'Nombor telefon tidak boleh melebihi 255 aksara.',
'image_url.max' => 'URL imej tidak boleh melebihi 255 aksara.',
'status.required' => 'Status diperlukan.',
'status.string' => 'Status aktif tidak sah.',
];
}
}
View File
@@ -0,0 +1,64 @@
<?php
namespace Modules\User\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Modules\Auth\Entities\User;
class UserActivationNotification extends Notification
{
use Queueable;
protected $newUser;
protected $sender;
/**
* Create a new notification instance.
*/
public function __construct(User $newUser, User $sender = null)
{
$this->newUser = $newUser;
$this->sender = $sender;
}
/**
* Get the notification's delivery channels.
*/
public function via($notifiable): array
{
return ['database'];
}
/**
* Get the array representation of the notification.
*/
public function toArray($notifiable): array
{
return [
'user_id' => $this->newUser->id,
'sender_id' => $this->sender ? $this->sender->id : null,
'type' => 'user_activation_required',
'message' => $this->getNotificationMessage(),
'user_name' => $this->newUser->name ?? 'Unknown',
'user_email' => $this->newUser->email ?? 'Unknown',
'user_army_number' => $this->newUser->army_number ?? 'Unknown',
'user_unit_name' => $this->newUser->unit->name ?? 'Unknown',
'user_status' => $this->newUser->status ?? 'pending',
];
}
/**
* Get notification message
*/
private function getNotificationMessage(): string
{
$userName = $this->newUser->name ?? 'Unknown';
$userEmail = $this->newUser->email ?? 'Unknown';
$userArmyNumber = $this->newUser->army_number ?? 'Unknown';
$unitName = $this->newUser->unit->name ?? 'Unknown';
return "Pengguna baru memerlukan pengaktifan. Nama: {$userName}, Email: {$userEmail}, No. Tentera: {$userArmyNumber}, Unit: {$unitName}";
}
}
View File
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace Modules\User\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use Modules\Auth\Entities\User;
class UserPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny($user): bool
{
return $user->hasPermissionTo('lihat pengguna');
}
/**
* Determine whether the user can view the model.
*/
public function view($user, ?User $userModel = null): bool
{
return $user->hasPermissionTo('lihat pengguna');
}
/**
* Determine whether the user can create models.
*/
public function create($user): bool
{
return $user->hasPermissionTo('tambah pengguna');
}
/**
* Determine whether the user can update the model.
*/
public function update($user, ?User $userModel = null): bool
{
return $user->hasPermissionTo('kemaskini pengguna');
}
/**
* Determine whether the user can delete any model.
*/
public function deleteAny($user): bool
{
return $user->hasPermissionTo('hapus pengguna');
}
/**
* Determine whether the user can delete the model.
*/
public function delete($user, ?User $userModel = null): bool
{
return $user->hasPermissionTo('hapus pengguna');
}
}
View File
@@ -0,0 +1,27 @@
<?php
namespace Modules\User\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event handler mappings for the application.
*
* @var array<string, array<int, string>>
*/
protected $listen = [];
/**
* Indicates if events should be discovered.
*
* @var bool
*/
protected static $shouldDiscoverEvents = true;
/**
* Configure the proper event listeners for email verification.
*/
protected function configureEmailVerification(): void {}
}
@@ -0,0 +1,49 @@
<?php
namespace Modules\User\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
protected string $name = 'User';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(): void
{
Route::middleware('web')->group(module_path($this->name, '/Routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::middleware('api')->name('api.')->group(module_path($this->name, '/Routes/api.php'));
}
}
@@ -0,0 +1,172 @@
<?php
namespace Modules\User\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
use Modules\Auth\Entities\User;
use Modules\User\Policies\UserPolicy;
use Nwidart\Modules\Traits\PathNamespace;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class UserServiceProvider extends ServiceProvider
{
use PathNamespace;
protected string $name = 'User';
protected string $nameLower = 'user';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->name, 'Database/Migrations'));
$this->registerPolicies();
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(EventServiceProvider::class);
$this->app->register(RouteServiceProvider::class);
// Register repository binding
$this->app->bind(
\Modules\User\Repositories\Contracts\UserRepositoryInterface::class,
\Modules\User\Repositories\UserRepository::class
);
}
/**
* Register policies.
*/
protected function registerPolicies(): void
{
Gate::policy(User::class, UserPolicy::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
// $this->commands([]);
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
// $this->app->booted(function () {
// $schedule = $this->app->make(Schedule::class);
// $schedule->command('inspire')->hourly();
// });
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->nameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->nameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->name, 'Lang'), $this->nameLower);
$this->loadJsonTranslationsFrom(module_path($this->name, 'Lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$configPath = module_path($this->name, config('modules.paths.generator.config.path'));
if (is_dir($configPath)) {
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configPath));
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$config = str_replace($configPath.DIRECTORY_SEPARATOR, '', $file->getPathname());
$config_key = str_replace([DIRECTORY_SEPARATOR, '.php'], ['.', ''], $config);
$segments = explode('.', $this->nameLower.'.'.$config_key);
// Remove duplicated adjacent segments
$normalized = [];
foreach ($segments as $segment) {
if (end($normalized) !== $segment) {
$normalized[] = $segment;
}
}
$key = ($config === 'config.php') ? $this->nameLower : implode('.', $normalized);
$this->publishes([$file->getPathname() => config_path($config)], 'config');
$this->merge_config_from($file->getPathname(), $key);
}
}
}
}
/**
* Merge config from the given path recursively.
*/
protected function merge_config_from(string $path, string $key): void
{
$existing = config($key, []);
$module_config = require $path;
config([$key => array_replace_recursive($existing, $module_config)]);
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->nameLower);
$sourcePath = module_path($this->name, 'Resources/Views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->nameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->nameLower);
Blade::componentNamespace(config('modules.namespace').'\\'.$this->name.'\\View\\Components', $this->nameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->nameLower)) {
$paths[] = $path.'/modules/'.$this->nameLower;
}
}
return $paths;
}
}
@@ -0,0 +1,50 @@
<?php
namespace Modules\User\Repositories\Contracts;
use Illuminate\Database\Eloquent\Collection;
use Modules\Auth\Entities\User;
interface UserRepositoryInterface
{
/**
* Get all Users with pagination and search
*/
public function getAllPaginated(int $perPage = 10, string $search = '');
/**
* Get all Users with their relationships and pagination with search
*/
public function getAllWithRelationsPaginated(
int $perPage = 10,
string $search = '',
string $status = '',
string $sortBy = 'name',
string $sortOrder = 'asc'
);
/**
* Get all Users with their relationships and search
*/
public function getAllWithRelations(string $search = ''): Collection;
/**
* Create a new User
*/
public function create(array $data): User;
/**
* Find User by ID
*/
public function findById(string $id): ?User;
/**
* Delete User (soft delete)
*/
public function delete(string $id): bool;
/**
* Get all Users
*/
public function all(string $search = ''): Collection;
}
@@ -0,0 +1,160 @@
<?php
namespace Modules\User\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Modules\Auth\Entities\User;
use Modules\User\Repositories\Contracts\UserRepositoryInterface;
class UserRepository implements UserRepositoryInterface
{
/**
* Get all Users with pagination and search
*/
public function getAllPaginated(int $perPage = 10, string $search = '')
{
$query = User::visibleTo(auth()->user())->excludeDevelopersUnlessDeveloper()->orderBy('name');
if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('name', 'ILIKE', "%{$search}%");
$q->orWhere('ic_number', 'ILIKE', "%{$search}%");
$q->orWhere('email', 'ILIKE', "%{$search}%");
$q->orWhere('phone_number', 'ILIKE', "%{$search}%");
$q->orWhereHas('roles', function ($roleQuery) use ($search) {
$roleQuery->whereRaw('LOWER(name) ILIKE ?', ['%' . strtolower($search) . '%']);
});
});
}
return $query->paginate($perPage);
}
/**
* Get all Users with their relationships and pagination with search
*/
public function getAllWithRelationsPaginated(
int $perPage = 10,
string $search = '',
string $status = '',
string $sortBy = 'name',
string $sortOrder = 'asc'
) {
$allowedSortColumns = [
'id',
'name',
'email',
'position',
'status',
'ic_number',
'phone_number',
'created_at',
];
$sortBy = in_array($sortBy, $allowedSortColumns, true) ? $sortBy : 'name';
$sortOrder = strtolower($sortOrder) === 'desc' ? 'desc' : 'asc';
$query = User::visibleTo(auth()->user())->excludeDevelopersUnlessDeveloper()
->with([
'roles:id,name,guard_name'
])
->orderBy($sortBy, $sortOrder);
if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('name', 'ILIKE', "%{$search}%")
->orWhere('ic_number', 'ILIKE', "%{$search}%")
->orWhere('email', 'ILIKE', "%{$search}%")
->orWhere('phone_number', 'ILIKE', "%{$search}%")
->orWhereHas('roles', function ($roleQuery) use ($search) {
$roleQuery->whereRaw('LOWER(name) ILIKE ?', ['%' . strtolower($search) . '%']);
});
$q->orWhere('ic_number', 'ILIKE', "%{$search}%");
$q->orWhere('email', 'ILIKE', "%{$search}%");
$q->orWhere('phone_number', 'ILIKE', "%{$search}%");
$q->orWhereHas('roles', function ($roleQuery) use ($search) {
$roleQuery->whereRaw('LOWER(name) ILIKE ?', ['%' . strtolower($search) . '%']);
});
});
}
// Add status filter
if (! empty($status)) {
$query->where('status', $status);
}
return $query->paginate($perPage);
}
/**
* Get all Users with their relationships and search
*/
public function getAllWithRelations(string $search = ''): Collection
{
$query = User::visibleTo(auth()->user())->excludeDevelopersUnlessDeveloper()->orderBy('name');
if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('name', 'ILIKE', "%{$search}%")
->orWhere('ic_number', 'ILIKE', "%{$search}%")
->orWhere('email', 'ILIKE', "%{$search}%")
->orWhere('phone_number', 'ILIKE', "%{$search}%")
->orWhereHas('roles', function ($roleQuery) use ($search) {
$roleQuery->whereRaw('LOWER(name) ILIKE ?', ['%' . strtolower($search) . '%']);
});
});
}
return $query->get();
}
/**
* Create a new User
*/
public function create(array $data): User
{
return User::create($data);
}
/**
* Find User by ID
*/
public function findById(string $id): ?User
{
return User::with(['roles'])->find($id);
}
/**
* Delete User (soft delete)
*/
public function delete(string $id): bool
{
$User = User::find($id);
if ($User) {
return $User->delete();
}
return false;
}
/**
* Get all Ranks
*/
public function all(string $search = ''): Collection
{
$query = User::visibleTo(auth()->user())->excludeDevelopersUnlessDeveloper()->orderBy('name');
if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('name', 'ILIKE', "%{$search}%")
->orWhere('ic_number', 'ILIKE', "%{$search}%")
->orWhere('email', 'ILIKE', "%{$search}%")
->orWhere('phone_number', 'ILIKE', "%{$search}%")
->orWhereHas('roles', function ($roleQuery) use ($search) {
$roleQuery->whereRaw('LOWER(name) ILIKE ?', ['%' . strtolower($search) . '%']);
});
});
}
return $query->get();
}
}
View File
+21
View File
@@ -0,0 +1,21 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\User\Http\Controllers\UserController;
use App\Http\Controllers\ImpersonateController;
Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(function () {
Route::apiResource('users', UserController::class)->names('user');
// User role management routes
Route::post('users/{user}/roles', [UserController::class, 'assignRoles'])->name('users.roles.assign');
// User profile management routes
Route::post('/profile', [UserController::class, 'updateProfile']);
Route::put('/profile/password', [UserController::class, 'updatePassword']);
// Impersonation routes
Route::get('/impersonate/take/{id}', [ImpersonateController::class, 'take'])->name('impersonate');
Route::get('/impersonate/leave', [ImpersonateController::class, 'leave'])->name('impersonate.leave');
Route::get('/impersonate/status', [ImpersonateController::class, 'status'])->name('impersonate.status');
});
View File
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace Modules\User\Services;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Modules\Auth\Entities\User;
use Modules\Role\Entities\Role;
use Modules\User\Repositories\Contracts\UserRepositoryInterface;
class UserService
{
public function __construct(
protected UserRepositoryInterface $repository
) {}
public function getPaginatedList(
int $perPage,
string $search,
string $status,
string $sortBy,
string $sortOrder
): LengthAwarePaginator {
return $this->repository->getAllWithRelationsPaginated(
$perPage,
$search,
$status,
$sortBy,
$sortOrder
);
}
public function getUserWithRelations(string $id): ?User
{
$user = $this->repository->findById($id);
if ($user) {
$user->load(['roles.permissions']);
}
return $user;
}
/**
* @return array{user: User}|array{error: string}
*/
public function assignRoles(string $id, array $roleIds): array
{
$user = $this->repository->findById($id);
if (! $user) {
return ['error' => 'User not found.'];
}
$roles = Role::whereIn('id', $roleIds)
->where('guard_name', 'api')
->get();
if ($roles->isEmpty()) {
return ['error' => 'No valid roles found.'];
}
$user->syncRoles($roles);
$user->load(['roles.permissions']);
return ['user' => $user];
}
public function updateProfile(User $user, array $validated, ?UploadedFile $image = null): User
{
if ($image) {
if ($user->image_url && Storage::disk('public')->exists($user->image_url)) {
Storage::disk('public')->delete($user->image_url);
}
$validated['image_url'] = $image->store('user-images', 'public');
}
unset($validated['image']);
$data = $this->prepareProfileUpdateData($validated);
$user->update($data);
$user->load(['roles.permissions']);
return $user;
}
/**
* @return array{}|array{error: string}
*/
public function updatePassword(User $user, string $currentPassword, string $newPassword): array
{
if (! Hash::check($currentPassword, $user->password)) {
return ['error' => 'Current password is incorrect.'];
}
$user->update([
'password' => Hash::make($newPassword),
]);
return [];
}
protected function prepareProfileUpdateData(array $validated): array
{
unset($validated['uuid']);
if (! array_key_exists('email', $validated)) {
unset($validated['email']);
}
return $validated;
}
}
View File
@@ -0,0 +1,35 @@
<?php
namespace Modules\User\Transformers;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Storage;
class UserListResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'ic_number' => $this->ic_number,
'position' => $this->position,
'phone_number' => $this->phone_number,
'image_url' => $this->image_url ? Storage::disk('public')->url($this->image_url) : null,
'status' => $this->status,
'email_verified_at' => $this->email_verified_at,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
'roles' => $this->roles->map(function ($role) {
return [
'id' => $role->id,
'name' => $role->name,
'guard_name' => $role->guard_name,
];
}),
];
}
}
@@ -0,0 +1,54 @@
<?php
namespace Modules\User\Transformers;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Storage;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'ic_number' => $this->ic_number,
'position' => $this->position,
'phone_number' => $this->phone_number,
'image_url' => $this->image_url ? Storage::disk('public')->url($this->image_url) : null,
'status' => $this->status,
'two_factor_secret' => $this->two_factor_secret,
'two_factor_recovery_codes' => $this->two_factor_recovery_codes,
'two_factor_confirmed_at' => $this->two_factor_confirmed_at,
'email_verified_at' => $this->email_verified_at,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
'roles' => $this->whenLoaded('roles', function () {
return $this->roles->map(function ($role) {
return [
'id' => $role->id,
'name' => $role->name,
'guard_name' => $role->guard_name,
'permissions' => $this->when($role->relationLoaded('permissions'), function () use ($role) {
return $role->permissions->map(function ($permission) {
return [
'id' => $permission->id,
'name' => $permission->name,
'guard_name' => $permission->guard_name,
'route_name' => $permission->route_name,
'created_at' => $permission->created_at,
'updated_at' => $permission->updated_at,
];
});
}),
'created_at' => $role->created_at,
'updated_at' => $role->updated_at,
];
});
}),
];
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "nwidart/user",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\User\\": "App",
"Modules\\User\\Database\\Factories\\": "database/factories/",
"Modules\\User\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\\User\\Tests\\": "tests/"
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "User",
"alias": "user",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\User\\Providers\\UserServiceProvider"
],
"files": []
}
+15
View File
@@ -0,0 +1,15 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.69.5",
"postcss": "^8.3.7",
"vite": "^4.0.0"
}
}