diff --git a/Modules/ActivityLog/Http/Controllers/ActivityLogController.php b/Modules/ActivityLog/Http/Controllers/ActivityLogController.php index fdbbe69..70f3d22 100644 --- a/Modules/ActivityLog/Http/Controllers/ActivityLogController.php +++ b/Modules/ActivityLog/Http/Controllers/ActivityLogController.php @@ -19,8 +19,6 @@ class ActivityLogController extends Controller { $this->authorize('viewAny', Activity::class); - // ActivityLogger::logView(new Activity, 'Viewed activity logs'); - $perPage = $request->get('per_page', 10); $perPage = min($perPage, 1000); // Limit max per page to 100 $search = trim((string) $request->get('search', '')); diff --git a/Modules/Auth/Entities/User.php b/Modules/Auth/Entities/User.php index d77d143..298ee43 100644 --- a/Modules/Auth/Entities/User.php +++ b/Modules/Auth/Entities/User.php @@ -11,6 +11,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Str; use Laravel\Sanctum\HasApiTokens; +use Spatie\Activitylog\Facades\Activity; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Permission\Contracts\Permission; @@ -105,22 +106,24 @@ class User extends Authenticatable implements MustVerifyEmail public function ensurePublicProfileToken(): string { - $expiryDays = self::publicProfileTokenTtlDays(); + return Activity::withoutLogs(function () { + $expiryDays = self::publicProfileTokenTtlDays(); + + if (blank($this->public_profile_token) || $this->isPublicProfileTokenExpired()) { + $this->forceFill([ + 'public_profile_token' => Str::random(48), + 'public_profile_token_expires_at' => now()->addDays($expiryDays), + ])->save(); + + return $this->public_profile_token; + } - if (blank($this->public_profile_token) || $this->isPublicProfileTokenExpired()) { $this->forceFill([ - 'public_profile_token' => Str::random(48), 'public_profile_token_expires_at' => now()->addDays($expiryDays), ])->save(); return $this->public_profile_token; - } - - $this->forceFill([ - 'public_profile_token_expires_at' => now()->addDays($expiryDays), - ])->save(); - - return $this->public_profile_token; + }); } /** @@ -189,6 +192,12 @@ class User extends Authenticatable implements MustVerifyEmail { return LogOptions::defaults() ->logAll() + ->logExcept([ + 'public_profile_token', + 'public_profile_token_expires_at', + 'remember_token', + 'updated_at', + ]) ->logOnlyDirty() ->dontSubmitEmptyLogs() ->setDescriptionForEvent(fn (string $eventName) => "User {$this->name} was {$eventName}"); diff --git a/Modules/Auth/Services/AuthSessionService.php b/Modules/Auth/Services/AuthSessionService.php index 8cd803c..d7d3fd5 100644 --- a/Modules/Auth/Services/AuthSessionService.php +++ b/Modules/Auth/Services/AuthSessionService.php @@ -7,45 +7,48 @@ use App\Support\AuthCookie; use Illuminate\Http\JsonResponse; use Modules\Auth\Entities\User; use Modules\Auth\Transformers\UserResource; +use Spatie\Activitylog\Facades\Activity; class AuthSessionService { public function createAuthResponse(User $user, string $message, int $status = 200): JsonResponse { - $user->ensurePublicProfileToken(); + return Activity::withoutLogs(function () use ($user, $message, $status) { + $user->ensurePublicProfileToken(); - if (config('app.env') !== 'local') { - $user->tokens()->delete(); - } + if (config('app.env') !== 'local') { + $user->tokens()->delete(); + } - $token = $user->createToken( - name: 'authToken', - abilities: ['*'], - expiresAt: now()->addMinutes((int) config('auth_cookie.lifetime_minutes', 720)) - ); + $token = $user->createToken( + name: 'authToken', + abilities: ['*'], + expiresAt: now()->addMinutes((int) config('auth_cookie.lifetime_minutes', 720)) + ); - $user->load(['roles.permissions']); + $user->load(['roles.permissions']); - ActiveRoleService::assignDefaultToToken($user, $token->accessToken); + ActiveRoleService::assignDefaultToToken($user, $token->accessToken); - $payload = [ - 'success' => true, - 'message' => $message, - 'data' => [ - 'user' => new UserResource($user), - 'token' => $token->plainTextToken, - 'token_type' => 'Bearer', - 'expires_at' => $token->accessToken->expires_at, - ], - ...ActiveRoleService::sessionMeta($user), - ]; + $payload = [ + 'success' => true, + 'message' => $message, + 'data' => [ + 'user' => new UserResource($user), + 'token' => $token->plainTextToken, + 'token_type' => 'Bearer', + 'expires_at' => $token->accessToken->expires_at, + ], + ...ActiveRoleService::sessionMeta($user), + ]; - if (AuthCookie::shouldExposeTokenInResponse()) { - $payload['data']['token'] = $token->plainTextToken; - } + if (AuthCookie::shouldExposeTokenInResponse()) { + $payload['data']['token'] = $token->plainTextToken; + } - $response = response()->json($payload, $status); + $response = response()->json($payload, $status); - return AuthCookie::attachAuthToken($response, $token->plainTextToken); + return AuthCookie::attachAuthToken($response, $token->plainTextToken); + }); } } diff --git a/Modules/Investment/Actions/.gitkeep b/Modules/Investment/Actions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Config/.gitkeep b/Modules/Investment/Config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Config/config.php b/Modules/Investment/Config/config.php new file mode 100644 index 0000000..8b6dd5a --- /dev/null +++ b/Modules/Investment/Config/config.php @@ -0,0 +1,5 @@ + 'Investment', +]; diff --git a/Modules/Investment/Console/.gitkeep b/Modules/Investment/Console/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Database/Factories/.gitkeep b/Modules/Investment/Database/Factories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Database/Migrations/.gitkeep b/Modules/Investment/Database/Migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Database/Migrations/2026_08_02_010000_create_investment_types_table.php b/Modules/Investment/Database/Migrations/2026_08_02_010000_create_investment_types_table.php new file mode 100644 index 0000000..5843694 --- /dev/null +++ b/Modules/Investment/Database/Migrations/2026_08_02_010000_create_investment_types_table.php @@ -0,0 +1,46 @@ +uuid('id')->primary(); + + $table->string('code'); + $table->index('code'); + $table->string('name'); + $table->text('description')->nullable(); + + $table->string('status')->default('ACTIVE'); + + $table->decimal('minimum_amount', 15, 2)->default(0); + $table->decimal('maximum_amount', 15, 2)->nullable(); + $table->decimal('total_fund_limit', 15, 2)->nullable(); + $table->decimal('reserved_amount', 15, 2)->default(0); + + $table->date('start_date')->nullable(); + $table->date('end_date')->nullable(); + + $table->boolean('allow_billplz')->default(true); + $table->boolean('allow_salary_deduction')->default(false); + + $table->string('billplz_collection_id')->nullable(); + + $table->foreignUuid('created_by')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignUuid('updated_by')->nullable()->constrained('users')->nullOnDelete(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('investment_types'); + } +}; diff --git a/Modules/Investment/Database/Seeders/.gitkeep b/Modules/Investment/Database/Seeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Database/Seeders/InvestmentDatabaseSeeder.php b/Modules/Investment/Database/Seeders/InvestmentDatabaseSeeder.php new file mode 100644 index 0000000..e055842 --- /dev/null +++ b/Modules/Investment/Database/Seeders/InvestmentDatabaseSeeder.php @@ -0,0 +1,18 @@ +call([ + InvestmentTypePermissionSeeder::class, + ]); + } +} diff --git a/Modules/Investment/Database/Seeders/InvestmentTypePermissionSeeder.php b/Modules/Investment/Database/Seeders/InvestmentTypePermissionSeeder.php new file mode 100644 index 0000000..b425269 --- /dev/null +++ b/Modules/Investment/Database/Seeders/InvestmentTypePermissionSeeder.php @@ -0,0 +1,26 @@ + $name, 'guard_name' => 'api'], + ['name' => $name, 'guard_name' => 'api'] + ); + } + } +} diff --git a/Modules/Investment/Emails/.gitkeep b/Modules/Investment/Emails/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Entities/.gitkeep b/Modules/Investment/Entities/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Entities/InvestmentType.php b/Modules/Investment/Entities/InvestmentType.php new file mode 100644 index 0000000..4463e32 --- /dev/null +++ b/Modules/Investment/Entities/InvestmentType.php @@ -0,0 +1,59 @@ + InvestmentTypeStatus::class, + 'minimum_amount' => 'decimal:2', + 'maximum_amount' => 'decimal:2', + 'total_fund_limit' => 'decimal:2', + 'reserved_amount' => 'decimal:2', + 'start_date' => 'date', + 'end_date' => 'date', + 'allow_billplz' => 'boolean', + 'allow_salary_deduction' => 'boolean', + ]; + } + + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function updatedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } +} diff --git a/Modules/Investment/Enums/InvestmentTypeStatus.php b/Modules/Investment/Enums/InvestmentTypeStatus.php new file mode 100644 index 0000000..f2529c6 --- /dev/null +++ b/Modules/Investment/Enums/InvestmentTypeStatus.php @@ -0,0 +1,10 @@ +id(); + $validated['updated_by'] = auth()->id(); + + return $validated; + } + + protected function prepareUpdateData(array $validated, $item): array + { + $validated['updated_by'] = auth()->id(); + + return $validated; + } + + protected function getItemName($item): string + { + return $item->name ?? $item->id; + } +} diff --git a/Modules/Investment/Http/Requests/.gitkeep b/Modules/Investment/Http/Requests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Http/Requests/InvestmentTypeRequest.php b/Modules/Investment/Http/Requests/InvestmentTypeRequest.php new file mode 100644 index 0000000..b7255d4 --- /dev/null +++ b/Modules/Investment/Http/Requests/InvestmentTypeRequest.php @@ -0,0 +1,63 @@ +isMethod('POST'); + $isUpdate = $this->isMethod('PUT') || $this->isMethod('PATCH'); + + if (! $isCreate && ! $isUpdate) { + return []; + } + + $codeRule = Rule::unique('investment_types', 'code')->whereNull('deleted_at'); + + if ($isUpdate) { + $codeRule = $codeRule->ignore($this->route('investment_type')); + } + + return [ + 'code' => ['required', 'string', 'max:50', 'alpha_dash', $codeRule], + 'name' => ['required', 'string', 'max:255'], + 'description' => ['nullable', 'string'], + 'status' => ['required', Rule::enum(InvestmentTypeStatus::class)], + 'minimum_amount' => ['required', 'numeric', 'min:0'], + 'maximum_amount' => ['nullable', 'numeric', 'gte:minimum_amount'], + 'total_fund_limit' => ['nullable', 'numeric', 'min:0'], + 'start_date' => ['nullable', 'date'], + 'end_date' => ['nullable', 'date', 'after_or_equal:start_date'], + 'allow_billplz' => ['required', 'boolean'], + 'allow_salary_deduction' => ['required', 'boolean'], + 'billplz_collection_id' => ['nullable', 'string', 'max:255'], + ]; + } + + public function messages(): array + { + return [ + 'code.required' => 'Kod jenis pelaburan diperlukan.', + 'code.unique' => 'Kod jenis pelaburan sudah wujud.', + 'code.alpha_dash' => 'Kod jenis pelaburan hanya boleh mengandungi huruf, nombor, sempang dan garis bawah.', + 'name.required' => 'Nama jenis pelaburan diperlukan.', + 'status.required' => 'Status jenis pelaburan diperlukan.', + 'minimum_amount.required' => 'Jumlah minimum diperlukan.', + 'minimum_amount.min' => 'Jumlah minimum tidak boleh negatif.', + 'maximum_amount.gte' => 'Jumlah maksimum mestilah sama atau lebih besar daripada jumlah minimum.', + 'end_date.after_or_equal' => 'Tarikh tamat mestilah pada atau selepas tarikh mula.', + 'allow_billplz.required' => 'Pilihan Billplz diperlukan.', + 'allow_salary_deduction.required' => 'Pilihan potongan gaji diperlukan.', + ]; + } +} diff --git a/Modules/Investment/Jobs/.gitkeep b/Modules/Investment/Jobs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Notifications/.gitkeep b/Modules/Investment/Notifications/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Policies/.gitkeep b/Modules/Investment/Policies/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Policies/InvestmentTypePolicy.php b/Modules/Investment/Policies/InvestmentTypePolicy.php new file mode 100644 index 0000000..96e6068 --- /dev/null +++ b/Modules/Investment/Policies/InvestmentTypePolicy.php @@ -0,0 +1,41 @@ +hasPermissionTo('lihat jenis pelaburan'); + } + + public function view($user, ?InvestmentType $investmentType = null): bool + { + return $user->hasPermissionTo('lihat jenis pelaburan'); + } + + public function create($user): bool + { + return $user->hasPermissionTo('tambah jenis pelaburan'); + } + + public function update($user, ?InvestmentType $investmentType = null): bool + { + return $user->hasPermissionTo('kemaskini jenis pelaburan'); + } + + public function deleteAny($user): bool + { + return $user->hasPermissionTo('hapus jenis pelaburan'); + } + + public function delete($user, ?InvestmentType $investmentType = null): bool + { + return $user->hasPermissionTo('hapus jenis pelaburan'); + } +} diff --git a/Modules/Investment/Providers/.gitkeep b/Modules/Investment/Providers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Providers/EventServiceProvider.php b/Modules/Investment/Providers/EventServiceProvider.php new file mode 100644 index 0000000..56f4cbf --- /dev/null +++ b/Modules/Investment/Providers/EventServiceProvider.php @@ -0,0 +1,27 @@ +> + */ + 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 {} +} diff --git a/Modules/Investment/Providers/InvestmentServiceProvider.php b/Modules/Investment/Providers/InvestmentServiceProvider.php new file mode 100644 index 0000000..27ff94e --- /dev/null +++ b/Modules/Investment/Providers/InvestmentServiceProvider.php @@ -0,0 +1,168 @@ +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); + + $this->app->bind( + \Modules\Investment\Repositories\Contracts\InvestmentTypeRepositoryInterface::class, + \Modules\Investment\Repositories\InvestmentTypeRepository::class + ); + } + + protected function registerPolicies(): void + { + Gate::policy(InvestmentType::class, InvestmentTypePolicy::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, '$PATH_LANG$'), $this->nameLower); + $this->loadJsonTranslationsFrom(module_path($this->name, '$PATH_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, '$PATH_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; + } +} diff --git a/Modules/Investment/Providers/RouteServiceProvider.php b/Modules/Investment/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..d4dc6ec --- /dev/null +++ b/Modules/Investment/Providers/RouteServiceProvider.php @@ -0,0 +1,50 @@ +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')); + } +} diff --git a/Modules/Investment/Repositories/.gitkeep b/Modules/Investment/Repositories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Repositories/Contracts/.gitkeep b/Modules/Investment/Repositories/Contracts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Repositories/Contracts/InvestmentTypeRepositoryInterface.php b/Modules/Investment/Repositories/Contracts/InvestmentTypeRepositoryInterface.php new file mode 100644 index 0000000..2648c79 --- /dev/null +++ b/Modules/Investment/Repositories/Contracts/InvestmentTypeRepositoryInterface.php @@ -0,0 +1,28 @@ +orderBy('name'); + } + + protected function applySearch($query, string $search) + { + if ($search !== '') { + $query->where(function ($q) use ($search) { + $q->where('name', 'ILIKE', "%{$search}%") + ->orWhere('code', 'ILIKE', "%{$search}%") + ->orWhere('description', 'ILIKE', "%{$search}%"); + }); + } + + return $query; + } + + public function getAllPaginated(int $perPage = 10, string $search = '') + { + $query = $this->baseQuery(); + $this->applySearch($query, $search); + + return $query->paginate($perPage); + } + + public function getAllWithRelationsPaginated( + int $perPage = 10, + string $search = '', + string $sortBy = 'name', + string $sortOrder = 'asc' + ) { + $allowedSortColumns = [ + 'id', + 'code', + 'name', + 'status', + 'minimum_amount', + 'maximum_amount', + 'total_fund_limit', + 'start_date', + 'end_date', + 'created_at', + ]; + $sortBy = in_array($sortBy, $allowedSortColumns, true) ? $sortBy : 'name'; + $sortOrder = strtolower($sortOrder) === 'desc' ? 'desc' : 'asc'; + + $query = InvestmentType::query()->orderBy($sortBy, $sortOrder); + $this->applySearch($query, $search); + + return $query->paginate($perPage); + } + + public function getAllWithRelations(string $search = ''): Collection + { + $query = $this->baseQuery(); + $this->applySearch($query, $search); + + return $query->get(); + } + + public function create(array $data): InvestmentType + { + return InvestmentType::create($data); + } + + public function findById(string $id): ?InvestmentType + { + return InvestmentType::query()->find($id); + } + + public function delete(string $id): bool + { + $investmentType = $this->findById($id); + + if ($investmentType) { + return (bool) $investmentType->delete(); + } + + return false; + } + + public function all(string $search = ''): Collection + { + $query = $this->baseQuery(); + $this->applySearch($query, $search); + + return $query->get(); + } +} diff --git a/Modules/Investment/Routes/.gitkeep b/Modules/Investment/Routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Routes/api.php b/Modules/Investment/Routes/api.php new file mode 100644 index 0000000..607d811 --- /dev/null +++ b/Modules/Investment/Routes/api.php @@ -0,0 +1,8 @@ +prefix('v1')->group(function () { + Route::apiResource('investment-types', InvestmentTypeController::class)->names('investment-type'); +}); diff --git a/Modules/Investment/Routes/web.php b/Modules/Investment/Routes/web.php new file mode 100644 index 0000000..c31fcc9 --- /dev/null +++ b/Modules/Investment/Routes/web.php @@ -0,0 +1,8 @@ +group(function () { + Route::resource('investments', InvestmentController::class)->names('investment'); +}); diff --git a/Modules/Investment/Services/.gitkeep b/Modules/Investment/Services/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Tests/Feature/.gitkeep b/Modules/Investment/Tests/Feature/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Tests/Unit/.gitkeep b/Modules/Investment/Tests/Unit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Transformers/.gitkeep b/Modules/Investment/Transformers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Modules/Investment/Transformers/InvestmentTypeResource.php b/Modules/Investment/Transformers/InvestmentTypeResource.php new file mode 100644 index 0000000..2704f2a --- /dev/null +++ b/Modules/Investment/Transformers/InvestmentTypeResource.php @@ -0,0 +1,33 @@ + $this->id, + 'code' => $this->code, + 'name' => $this->name, + 'description' => $this->description, + 'status' => $this->status?->value ?? $this->status, + 'minimum_amount' => $this->minimum_amount, + 'maximum_amount' => $this->maximum_amount, + 'total_fund_limit' => $this->total_fund_limit, + 'reserved_amount' => $this->reserved_amount, + 'start_date' => $this->start_date?->toDateString(), + 'end_date' => $this->end_date?->toDateString(), + 'allow_billplz' => (bool) $this->allow_billplz, + 'allow_salary_deduction' => (bool) $this->allow_salary_deduction, + 'billplz_collection_id' => $this->billplz_collection_id, + 'created_by' => $this->created_by, + 'updated_by' => $this->updated_by, + 'created_at' => $this->created_at?->toISOString(), + 'updated_at' => $this->updated_at?->toISOString(), + ]; + } +} diff --git a/Modules/Investment/composer.json b/Modules/Investment/composer.json new file mode 100644 index 0000000..f6599db --- /dev/null +++ b/Modules/Investment/composer.json @@ -0,0 +1,30 @@ +{ + "name": "nwidart/investment", + "description": "", + "authors": [ + { + "name": "Nicolas Widart", + "email": "n.widart@gmail.com" + } + ], + "extra": { + "laravel": { + "providers": [], + "aliases": { + + } + } + }, + "autoload": { + "psr-4": { + "Modules\\Investment\\": "App", + "Modules\\Investment\\Database\\Factories\\": "database/factories/", + "Modules\\Investment\\Database\\Seeders\\": "database/seeders/" + } + }, + "autoload-dev": { + "psr-4": { + "Modules\\Investment\\Tests\\": "tests/" + } + } +} diff --git a/Modules/Investment/module.json b/Modules/Investment/module.json new file mode 100644 index 0000000..649d0fa --- /dev/null +++ b/Modules/Investment/module.json @@ -0,0 +1,11 @@ +{ + "name": "Investment", + "alias": "investment", + "description": "", + "keywords": [], + "priority": 0, + "providers": [ + "Modules\\Investment\\Providers\\InvestmentServiceProvider" + ], + "files": [] +} diff --git a/Modules/Investment/package.json b/Modules/Investment/package.json new file mode 100644 index 0000000..d6fbfc8 --- /dev/null +++ b/Modules/Investment/package.json @@ -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" + } +} diff --git a/Modules/Role/Http/Controllers/RoleController.php b/Modules/Role/Http/Controllers/RoleController.php index 6320d20..25494ca 100644 --- a/Modules/Role/Http/Controllers/RoleController.php +++ b/Modules/Role/Http/Controllers/RoleController.php @@ -3,7 +3,6 @@ namespace Modules\Role\Http\Controllers; use App\Http\Controllers\BaseCrudController; -use App\Services\ActivityLogger; use Exception; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; @@ -69,8 +68,6 @@ class RoleController extends BaseCrudController // Use repository update method to handle permissions $updatedItem = $this->repository->update($id, $data); - ActivityLogger::log("Updated {$this->resourceName}: {$this->getItemName($updatedItem)}", $updatedItem); - return response()->json([ 'success' => true, 'data' => new $this->resourceClass($updatedItem), diff --git a/Modules/User/Http/Controllers/UserController.php b/Modules/User/Http/Controllers/UserController.php index 85cd8b8..623649e 100644 --- a/Modules/User/Http/Controllers/UserController.php +++ b/Modules/User/Http/Controllers/UserController.php @@ -3,7 +3,6 @@ namespace Modules\User\Http\Controllers; use App\Http\Controllers\BaseCrudController; -use App\Services\ActivityLogger; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -324,8 +323,6 @@ class UserController extends BaseCrudController ], 404); } - ActivityLogger::log("Restored {$this->resourceName}: {$user->name}", $user); - return response()->json([ 'success' => true, 'message' => 'User restored successfully.', diff --git a/app/Http/Controllers/BaseCrudController.php b/app/Http/Controllers/BaseCrudController.php index badbade..4e8e926 100644 --- a/app/Http/Controllers/BaseCrudController.php +++ b/app/Http/Controllers/BaseCrudController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers; -use App\Services\ActivityLogger; use App\Traits\NotifiesAdmins; use Exception; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; diff --git a/app/Services/ActivityLogger.php b/app/Services/ActivityLogger.php index e775ab2..105ae74 100644 --- a/app/Services/ActivityLogger.php +++ b/app/Services/ActivityLogger.php @@ -22,23 +22,6 @@ class ActivityLogger return $activity; } - public static function logLogin(string $email): void - { - self::log("User logged in with email: {$email}", null, [ - 'email' => $email, - 'ip_address' => request()->ip(), - 'user_agent' => request()->userAgent(), - ], 'authentication'); - } - - public static function logLogout(): void - { - self::log('User logged out', null, [ - 'ip_address' => request()->ip(), - 'user_agent' => request()->userAgent(), - ], 'authentication'); - } - public static function logView(Model $model, ?string $customDescription = null): void { $modelName = class_basename($model); diff --git a/modules_statuses.json b/modules_statuses.json index ea6b6af..117a790 100644 --- a/modules_statuses.json +++ b/modules_statuses.json @@ -9,5 +9,6 @@ "MembershipApplication": true, "Activity": true, "ExternalSystem": true, - "Feedback": true + "Feedback": true, + "Investment": true } \ No newline at end of file