diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 34006820..d624d815 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -14,7 +14,9 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { - // $schedule->command('inspire')->hourly(); +// $schedule->command('inspire')->hourly(); + $schedule->command('model:prune')->daily(); + } /** diff --git a/app/Events/TicketCreated.php b/app/Events/Ticket/TicketCreated.php similarity index 94% rename from app/Events/TicketCreated.php rename to app/Events/Ticket/TicketCreated.php index 5eed8bd4..abdef986 100644 --- a/app/Events/TicketCreated.php +++ b/app/Events/Ticket/TicketCreated.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Events; +namespace App\Events\Ticket; use App\Models\Ticket; use Illuminate\Broadcasting\InteractsWithSockets; diff --git a/app/Events/TicketReplyReceived.php b/app/Events/Ticket/TicketReplyReceived.php similarity index 94% rename from app/Events/TicketReplyReceived.php rename to app/Events/Ticket/TicketReplyReceived.php index cb12ee6a..5fcf055b 100644 --- a/app/Events/TicketReplyReceived.php +++ b/app/Events/Ticket/TicketReplyReceived.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Events; +namespace App\Events\Ticket; use App\Models\TicketMessage; use Illuminate\Broadcasting\InteractsWithSockets; diff --git a/app/Events/TicketUpdated.php b/app/Events/Ticket/TicketUpdated.php similarity index 94% rename from app/Events/TicketUpdated.php rename to app/Events/Ticket/TicketUpdated.php index 27ebec1b..4771cc9f 100644 --- a/app/Events/TicketUpdated.php +++ b/app/Events/Ticket/TicketUpdated.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Events; +namespace App\Events\Ticket; use App\Models\Ticket; use Illuminate\Broadcasting\InteractsWithSockets; diff --git a/app/Events/User/UserDeleting.php b/app/Events/User/UserDeleting.php new file mode 100644 index 00000000..4c0f4e57 --- /dev/null +++ b/app/Events/User/UserDeleting.php @@ -0,0 +1,27 @@ +user = $user; + } + + +} diff --git a/app/Filament/Resources/OrganizationResource/Widgets/BaseOrganizationsWidget.php b/app/Filament/Resources/OrganizationResource/Widgets/BaseOrganizationsWidget.php index f1dc7515..c35cd6d0 100644 --- a/app/Filament/Resources/OrganizationResource/Widgets/BaseOrganizationsWidget.php +++ b/app/Filament/Resources/OrganizationResource/Widgets/BaseOrganizationsWidget.php @@ -13,6 +13,7 @@ use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\Layout; use Filament\Tables\Filters\SelectFilter; +use Filament\Tables\Filters\TernaryFilter; use Filament\Widgets\TableWidget as BaseWidget; use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Database\Eloquent\Builder; @@ -116,35 +117,53 @@ protected function getTableFilters(): array ->placeholder(__('organization.filters.activity_domains_placeholder')) ->multiple(), - Filter::make('accepts_volunteers') - ->toggle() + TernaryFilter::make('accepts_volunteers') ->label(__('organization.filters.accepts_volunteers')) - ->query(fn (Builder $query) => $query->whereAcceptsVolunteers()), + ->queries( + true: fn (Builder $query) => $query->whereAcceptsVolunteers(), + false: fn (Builder $query) => $query->whereDoesntAcceptsVolunteers(), + blank: fn (Builder $query) => $query, + ), - Filter::make('has_volunteers') - ->toggle() + TernaryFilter::make('has_volunteers') ->label(__('organization.filters.has_volunteers')) - ->query(fn (Builder $query) => $query->whereHasVolunteers()), + ->queries( + true: fn (Builder $query) => $query->whereHasVolunteers(), + false: fn (Builder $query) => $query->whereDoesntHaveVolunteers(), + blank: fn (Builder $query) => $query, + ), - Filter::make('has_projects') - ->toggle() + TernaryFilter::make('has_projects') ->label(__('organization.filters.has_projects')) - ->query(fn (Builder $query) => $query->whereHasProjects()), + ->queries( + true: fn (Builder $query) => $query->whereHasProjects(), + false: fn (Builder $query) => $query->whereDoesntHaveProjects(), + blank: fn (Builder $query) => $query, + ), - Filter::make('has_active_projects') - ->toggle() + TernaryFilter::make('has_active_projects') ->label(__('organization.filters.has_active_projects')) - ->query(fn (Builder $query) => $query->whereHasActiveProjects()), + ->queries( + true: fn (Builder $query) => $query->whereHasActiveProjects(), + false: fn (Builder $query) => $query->whereDoesntHaveActiveProjects(), + blank: fn (Builder $query) => $query, + ), - Filter::make('has_eu_platesc') - ->toggle() + TernaryFilter::make('has_eu_platesc') ->label(__('organization.filters.has_eu_platesc')) - ->query(fn (Builder $query) => $query->whereHasEuPlatesc()), + ->queries( + true: fn (Builder $query) => $query->whereHasEuPlatesc(), + false: fn (Builder $query) => $query->whereDoesntHaveEuPlatesc(), + blank: fn (Builder $query) => $query, + ), - Filter::make('has_donations') - ->toggle() + TernaryFilter::make('has_donations') ->label(__('organization.filters.has_donations')) - ->query(fn (Builder $query) => $query->whereHasDonations()), + ->queries( + true: fn (Builder $query) => $query->whereHasDonations(), + false: fn (Builder $query) => $query->whereDoesntHaveDonations(), + blank: fn (Builder $query) => $query, + ) ]; } diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 70b5bb0d..bf510c13 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -15,6 +15,7 @@ use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; @@ -78,6 +79,7 @@ public function store(RegistrationRequest $request): RedirectResponse $user->organization_id = $organization->id; $user->save(); } + Auth::login($user); return redirect()->route('register')->with('success_message', ['message' => 'Contul a fost creat', 'usrid' => $user['id']]); } catch(\Throwable $th) { diff --git a/app/Listeners/SendTicketCreatedNotification.php b/app/Listeners/Ticket/SendTicketCreatedNotification.php similarity index 91% rename from app/Listeners/SendTicketCreatedNotification.php rename to app/Listeners/Ticket/SendTicketCreatedNotification.php index dbed5346..f3ea4ae0 100644 --- a/app/Listeners/SendTicketCreatedNotification.php +++ b/app/Listeners/Ticket/SendTicketCreatedNotification.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Listeners; +namespace App\Listeners\Ticket; -use App\Events\TicketCreated; +use App\Events\Ticket\TicketCreated; use App\Models\User; use App\Notifications\Admin; use App\Notifications\Ngo; diff --git a/app/Listeners/SendTicketReplyReceivedNotification.php b/app/Listeners/Ticket/SendTicketReplyReceivedNotification.php similarity index 90% rename from app/Listeners/SendTicketReplyReceivedNotification.php rename to app/Listeners/Ticket/SendTicketReplyReceivedNotification.php index ec5abb0b..c2b684b9 100644 --- a/app/Listeners/SendTicketReplyReceivedNotification.php +++ b/app/Listeners/Ticket/SendTicketReplyReceivedNotification.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Listeners; +namespace App\Listeners\Ticket; -use App\Events\TicketReplyReceived; +use App\Events\Ticket\TicketReplyReceived; use App\Models\User; use App\Notifications\Admin; use App\Notifications\Ngo; diff --git a/app/Listeners/SendTicketStatusChangedNotification.php b/app/Listeners/Ticket/SendTicketStatusChangedNotification.php similarity index 93% rename from app/Listeners/SendTicketStatusChangedNotification.php rename to app/Listeners/Ticket/SendTicketStatusChangedNotification.php index 8a94a6d5..9074254a 100644 --- a/app/Listeners/SendTicketStatusChangedNotification.php +++ b/app/Listeners/Ticket/SendTicketStatusChangedNotification.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Listeners; +namespace App\Listeners\Ticket; -use App\Events\TicketUpdated; +use App\Events\Ticket\TicketUpdated; use App\Models\User; use App\Notifications\Admin; use App\Notifications\Ngo; diff --git a/app/Listeners/User/UserDeletingListener.php b/app/Listeners/User/UserDeletingListener.php new file mode 100644 index 00000000..a007eb7d --- /dev/null +++ b/app/Listeners/User/UserDeletingListener.php @@ -0,0 +1,24 @@ +user->loadMissing('organization'); + Log::info('UserDeletingListener', [ + 'user' => $user->id, + 'organization' => $event->user->organization->id, + ]); + $user->organization->delete(); + } +} diff --git a/app/Models/Organization.php b/app/Models/Organization.php index 3599fbda..c37192be 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -143,32 +143,64 @@ public function scopeWhereAcceptsVolunteers(Builder $query): Builder return $query->where('accepts_volunteers', true); } + public function scopeWhereDoesntAcceptsVolunteers(Builder $query): Builder + { + return $query->where('accepts_volunteers', false); + } + public function scopeWhereHasVolunteers(Builder $query): Builder { return $query->whereHas('volunteers'); } + public function scopeWhereDoesntHaveVolunteers(Builder $query): Builder + { + return $query->whereDoesntHave('volunteers'); + } + public function scopeWhereHasProjects(Builder $query): Builder { return $query->whereHas('projects'); } + public function scopeWhereDoesntHaveProjects(Builder $query): Builder + { + return $query->whereDoesntHave('projects'); + } + + public function scopeWhereHasActiveProjects(Builder $query): Builder { return $query->whereRelation('projects', 'status', ProjectStatus::active); } + public function scopeWhereDoesntHaveActiveProjects(Builder $query): Builder + { + return $query->whereRelation('projects', 'status', '!=', ProjectStatus::active); + } + + public function scopeWhereHasEuPlatesc(Builder $query): Builder { return $query->whereNotNull('eu_platesc_merchant_id') ->whereNotNull('eu_platesc_private_key'); } + public function scopeWhereDoesntHaveEuPlatesc(Builder $query): Builder + { + return $query->whereNull('eu_platesc_merchant_id') + ->orWhereNull('eu_platesc_private_key'); + } public function scopeWhereHasDonations(Builder $query): Builder { return $query->whereHas('projects.donations'); } + public function scopeWhereDoesntHaveDonations(Builder $query): Builder + { + return $query->whereDoesntHave('projects.donations'); + } + public function getAdministrators(): Collection { return $this->users() diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index cd29ad97..1ebabe3f 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -4,8 +4,8 @@ namespace App\Models; -use App\Events\TicketCreated; -use App\Events\TicketUpdated; +use App\Events\Ticket\TicketCreated; +use App\Events\Ticket\TicketUpdated; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; diff --git a/app/Models/TicketMessage.php b/app/Models/TicketMessage.php index 7476ef7e..f8fdfe5c 100644 --- a/app/Models/TicketMessage.php +++ b/app/Models/TicketMessage.php @@ -4,7 +4,7 @@ namespace App\Models; -use App\Events\TicketReplyReceived; +use App\Events\Ticket\TicketReplyReceived; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; diff --git a/app/Models/User.php b/app/Models/User.php index 1e69eb9e..3d299142 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,24 +4,28 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; use App\Concerns\MustSetInitialPassword; +use App\Events\User\UserDeleting; use App\Traits\HasRole; use Filament\Models\Contracts\FilamentUser; +use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Prunable; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; -class User extends Authenticatable implements FilamentUser +class User extends Authenticatable implements FilamentUser, MustVerifyEmail { use HasApiTokens; use HasFactory; use Notifiable; use HasRole; use MustSetInitialPassword; + use Prunable; /** * The attributes that are mass assignable. @@ -58,6 +62,10 @@ class User extends Authenticatable implements FilamentUser 'email_verified_at' => 'datetime', ]; + protected $dispatchesEvents = [ + 'deleting' => UserDeleting::class, + ]; + public function badges(): BelongsToMany { return $this->belongsToMany(Badge::class) @@ -77,11 +85,17 @@ public function currentOrganization(): Organization public function canAccessFilament(): bool { - return $this->isBBAdmin() || $this->isBBManager(); + return $this->isBBAdmin() || $this->isBBManager(); } public function getFilamentName(): string { return "{$this->name}"; } + + public function prunable(): Builder + { + return static::whereNull('email_verified_at')->where('created_at', '<=', now()->subHours(48)); + } + } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 3e888cc0..c37df3b4 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -9,7 +9,9 @@ use App\Models\Project; use App\Policies\OrganizationPolicy; use App\Policies\ProjectPolicy; +use Illuminate\Auth\Notifications\VerifyEmail; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; +use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider @@ -32,5 +34,11 @@ public function boot(): void Gate::define('view-project', function ($user) { return $user->isAdmin(); }); + VerifyEmail::toMailUsing(function ($notifiable, $url) { + return (new MailMessage) + ->subject(__('auth.mail.verify_email.subject')) + ->line(__('auth.mail.verify_email.line_1')) + ->action(__('auth.mail.verify_email.action'), $url); + }); } } diff --git a/database/migrations/2023_05_05_142245_update_users.php b/database/migrations/2023_05_05_142245_update_users.php index 2dfd4485..93656ec7 100644 --- a/database/migrations/2023_05_05_142245_update_users.php +++ b/database/migrations/2023_05_05_142245_update_users.php @@ -19,7 +19,7 @@ public function up(): void $table->string('phone')->nullable(); $table->string('source_of_information')->nullable(); $table->timestamp('password_set_at')->nullable(); - $table->foreignIdFor(Organization::class)->nullable()->constrained(); + $table->foreignIdFor(Organization::class)->nullable()->constrained()->onDelete('cascade'); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 6711e2a5..89cb400d 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -78,9 +78,9 @@ public function run(): void ->count(10) ->hasVolunteers(10) ) -// ->has( -//// Ticket::factory() -// ) + ->has( + Ticket::factory() + ) ->create(); Badge::factory() diff --git a/lang/ro/auth.php b/lang/ro/auth.php index 1d39960d..662fe6f6 100644 --- a/lang/ro/auth.php +++ b/lang/ro/auth.php @@ -6,6 +6,13 @@ 'failed' => 'Datele de identificare nu pot fi confirmate.', 'password' => 'Parola este greșită.', 'throttle' => 'Prea multe încercări de intrare în cont. Puteți încerca din nou peste :seconds secunde.', + 'mail' =>[ + 'verify_email'=>[ + 'subject' => 'Verifică-ți adresa de email', + 'line_1' => 'Te rugăm să confirmi adresa de email apăsând pe butonul de mai jos.', + 'action' => 'Confirmă adresa de email', + ], + ], 'welcome' => [ 'already_used' => 'Parola a fost deja setată.', 'greeting' => 'Bun venit, :name!', diff --git a/resources/js/Pages/Auth/VerifyEmail.vue b/resources/js/Pages/Auth/VerifyEmail.vue index b893bf8c..89e6efd7 100644 --- a/resources/js/Pages/Auth/VerifyEmail.vue +++ b/resources/js/Pages/Auth/VerifyEmail.vue @@ -1,3 +1,28 @@ + diff --git a/resources/js/locales/ro.js b/resources/js/locales/ro.js index 58462d48..5f900e8f 100644 --- a/resources/js/locales/ro.js +++ b/resources/js/locales/ro.js @@ -403,4 +403,7 @@ export default { 'confirm_reopen_ticket': 'Ești sigur că vrei să redeschizi acest tichet?', "add_regional_project": "Adaugă un proiect regional", "field_has_pending_changes":'Acest câmp are modificări neaprobare. Dacă il editezi, modificările vor fi pierdute.', + 'verify_email_title':'Îți mulțumim că te-ai înregistrat pe platforma Bursa Binelui !', + "verify_email_description" : "Te rugăm să îți validezi adresa de email pentru a-ți accesa contul. Dacă nu ai primit un email pentru validare, te rugăm să ne contactezi la email.", + 'verify_email_warning':'Dacă adresa de email nu va fi validată în 48 de ore, datele tale vor fi șterse și nu vor putea fi recuperate', } diff --git a/routes/auth.php b/routes/auth.php index b7e796c6..f8019277 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -39,10 +39,16 @@ Route::get('ngo/welcome/{user}', [PasswordController::class, 'setInitialPassword'])->name('ngo.user.welcome'); Route::post('ngo/welcome/{user}', [PasswordController::class, 'storeInitialPassword'])->name('ngo.user.welcome.store'); + + Route::middleware('auth')->group(function () { Route::get('verify-email', EmailVerificationPromptController::class)->name('verification.notice'); Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)->middleware(['signed', 'throttle:6,1'])->name('verification.verify'); + Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout'); +}); + +Route::middleware(['auth','verified'])->group(function () { Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])->middleware('throttle:6,1')->name('verification.send'); Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])->name('password.confirm'); @@ -50,5 +56,4 @@ Route::put('password', [PasswordController::class, 'update'])->name('password.update'); - Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout'); }); diff --git a/routes/donations.php b/routes/donations.php index c0cfff4a..0e108626 100644 --- a/routes/donations.php +++ b/routes/donations.php @@ -10,7 +10,7 @@ Route::get('/doneaza/callbacks/{uuid}', [\App\Http\Controllers\DonationController::class, 'makeDonation'])->name('donation.callback'); Route::post('/doneaza/thanks/{uuid}', [\App\Http\Controllers\DonationController::class, 'thankYou'])->name('donation.thanks'); /* Tichets routes. */ -Route::prefix('ong')->middleware('auth')->group(function () { +Route::prefix('ong')->middleware(['auth','verified'])->group(function () { Route::get('/donatii', function () { return Inertia::render('AdminOng/Donations/Donations'); })->name('admin.ong.donations'); diff --git a/routes/organizations.php b/routes/organizations.php index f7df6236..c77fd9c3 100644 --- a/routes/organizations.php +++ b/routes/organizations.php @@ -11,7 +11,7 @@ Route::post('/organizatie/{organization}/voluntar', [OrganizationController::class, 'volunteer'])->name('organization.volunteer'); /* Admin Ong routes. */ -Route::prefix('ong')->middleware('auth')->group(function () { +Route::prefix('ong')->middleware(['auth','verified'])->group(function () { Route::get('organizatie', [OrganizationController::class, 'edit'])->name('admin.ong.edit'); Route::delete('organizatie/remove-logo', [OrganizationController::class, 'removeLogo'])->name('organization.remove_logo'); Route::post('organizatie/update/{organization}', [OrganizationController::class, 'update'])->name('admin.ong.update'); diff --git a/routes/projects.php b/routes/projects.php index a8857bb9..5fa1d236 100644 --- a/routes/projects.php +++ b/routes/projects.php @@ -13,7 +13,7 @@ Route::post('/proiect/{project:slug}/voluntar', [\App\Http\Controllers\ProjectController::class, 'volunteer'])->name('project.volunteer'); /* Ong routes. */ -Route::prefix('ong')->middleware('auth')->group(function () { +Route::prefix('ong')->middleware(['auth','verified'])->group(function () { Route::get('proiecte', [ProjectController::class, 'index'])->name('admin.ong.projects'); Route::post('project/change-status/{project}', [ProjectController::class, 'changeStatus'])->name('admin.ong.project.change-status'); Route::get('add-proiect', [ProjectController::class, 'create'])->name('admin.ong.project.add'); diff --git a/routes/tickets.php b/routes/tickets.php index 45f5fba0..e8a3b1f7 100644 --- a/routes/tickets.php +++ b/routes/tickets.php @@ -6,7 +6,7 @@ use Illuminate\Support\Facades\Route; /* Tichets routes. */ -Route::prefix('ong/tickets')->middleware('auth')->group(function () { +Route::prefix('ong/tickets')->middleware(['auth','verified'])->group(function () { Route::get('/{status}', [TicketController::class, 'index']) ->whereIn('status', ['open', 'closed']) ->name('admin.ong.tickets.index'); diff --git a/routes/volunteers.php b/routes/volunteers.php index 693dd238..6d5e3095 100644 --- a/routes/volunteers.php +++ b/routes/volunteers.php @@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Route; -Route::prefix('ong')->middleware('auth')->group(function () { +Route::prefix('ong')->middleware(['auth','verified'])->group(function () { Route::get('voluntari/{status?}', [\App\Http\Controllers\Ngo\VolunteerController::class, 'index'])->name('admin.ong.volunteers'); Route::post('voluntari/approve/{id}', [\App\Http\Controllers\Ngo\VolunteerController::class, 'approve'])->name('admin.ong.volunteers.approve'); Route::post('voluntari/reject/{id}', [\App\Http\Controllers\Ngo\VolunteerController::class, 'reject'])->name('admin.ong.volunteers.reject'); diff --git a/routes/web.php b/routes/web.php index 371053e3..b1d5c4cc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -29,7 +29,7 @@ Route::get('/dashboard', DashboardController::class)->middleware(['auth', 'verified'])->name('dashboard'); -Route::middleware('auth')->group(function () { +Route::middleware(['auth','verified'])->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');