Эх сурвалжийг харах

feat: add mesma logica de validacao da foto de perfil que existe em prestador para o cliente

Gustavo Mantovani 1 долоо хоног өмнө
parent
commit
f3ae83fc05

+ 7 - 0
app/Http/Requests/ClientRequest.php

@@ -3,6 +3,7 @@
 namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
 
 class ClientRequest extends FormRequest
 {
@@ -13,6 +14,12 @@ class ClientRequest extends FormRequest
         $rules = [
             'avatar' => 'sometimes|file|image|mimes:jpg,jpeg,png,webp|max:5120',
 
+            'selfie_verified' => [
+                'sometimes',
+                'boolean',
+                Rule::prohibitedIf(fn () => ! $this->user()?->canModerateClients()),
+            ],
+
             'document' => [
                 'sometimes',
                 'string',

+ 3 - 2
app/Http/Resources/ClientResource.php

@@ -18,8 +18,9 @@ class ClientResource extends JsonResource
             'id'               => $this->id,
             'document'         => $this->document,
             'user_id'          => $this->user_id,
-            'profile_media_id' => $this->profile_media_id,
-            'profile_media'    => new MediaResource($this->whenLoaded('profileMedia')),
+            'selfie_verified'  => $this->selfie_verified,
+            'profile_media_id' => $this->profileMedia?->id,
+            'profile_media'    => $this->profileMedia ? new MediaResource($this->profileMedia) : null,
             'user'             => new UserResource($this->whenLoaded('user')),
             'created_at'       => $this->created_at,
             'updated_at'       => $this->updated_at,

+ 23 - 4
app/Models/Client.php

@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\Relations\HasOne;
 use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Facades\Auth;
 
 /**
  * @property int $id
@@ -21,6 +22,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property string|null $gateway_customer_id
  * @property string|null $gateway_customer_code
  * @property int|null $profile_media_id
+ * @property bool $selfie_verified
  * @property string|null $idempotency_key
  * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProviderClientBlock> $blockedByProviders
  * @property-read int|null $blocked_by_providers_count
@@ -61,12 +63,14 @@ class Client extends Model
         'idempotency_key',
         'user_id',
         'profile_media_id',
+        'selfie_verified',
     ];
 
     protected $casts = [
-        'created_at' => 'datetime',
-        'updated_at' => 'datetime',
-        'deleted_at' => 'datetime',
+        'created_at'      => 'datetime',
+        'updated_at'      => 'datetime',
+        'deleted_at'      => 'datetime',
+        'selfie_verified' => 'boolean',
     ];
 
     public function user(): BelongsTo
@@ -88,7 +92,22 @@ class Client extends Model
 
     public function profileMedia(): BelongsTo
     {
-        return $this->belongsTo(Media::class, 'profile_media_id');
+        $authenticatedUserId = Auth::id();
+
+        return $this->belongsTo(Media::class, 'profile_media_id')
+            ->join('clients as profile_media_clients', function ($join) {
+                $join->on('profile_media_clients.id', '=', 'media.source_id')
+                    ->on('profile_media_clients.profile_media_id', '=', 'media.id');
+            })
+            ->where('media.source', 'client')
+            ->where(function ($query) use ($authenticatedUserId) {
+                $query->where('profile_media_clients.selfie_verified', true);
+
+                if ($authenticatedUserId !== null) {
+                    $query->orWhere('profile_media_clients.user_id', $authenticatedUserId);
+                }
+            })
+            ->select('media.*');
     }
 
     public function schedules()

+ 5 - 0
app/Models/User.php

@@ -98,6 +98,11 @@ class User extends Authenticatable
         return $this->isAdmin();
     }
 
+    public function canModerateClients(): bool
+    {
+        return $this->isAdmin();
+    }
+
     //
 
     public function client()

+ 15 - 0
app/Observers/ClientObserver.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Observers;
+
+use App\Models\Client;
+
+class ClientObserver
+{
+    public function updating(Client $client): void
+    {
+        if ($client->isDirty('profile_media_id')) {
+            $client->selfie_verified = false;
+        }
+    }
+}

+ 3 - 0
app/Providers/AppServiceProvider.php

@@ -2,8 +2,10 @@
 
 namespace App\Providers;
 
+use App\Models\Client;
 use App\Models\Provider;
 use App\Models\Schedule;
+use App\Observers\ClientObserver;
 use App\Observers\ProviderObserver;
 use App\Observers\ScheduleObserver;
 use Illuminate\Support\ServiceProvider;
@@ -32,6 +34,7 @@ class AppServiceProvider extends ServiceProvider
      */
     public function boot(): void
     {
+        Client::observe(ClientObserver::class);
         Provider::observe(ProviderObserver::class);
 
         if ($this->app->environment(['local', 'development', 'dev'])) {

+ 38 - 26
app/Services/DashboardService.php

@@ -752,10 +752,10 @@ class DashboardService
         ->where('schedules.status', 'pending')
         ->leftJoin('clients', 'clients.id', '=', 'schedules.client_id')
         ->leftJoin('users as client_user', 'client_user.id', '=', 'clients.user_id')
-        ->leftJoin('media as client_media', 'client_media.id', '=', 'clients.profile_media_id')
         ->leftJoin('custom_schedules', 'custom_schedules.schedule_id', '=', 'schedules.id')
         ->select(
             'schedules.id',
+            'schedules.client_id',
             'client_user.name as client_name',
             'clients.average_rating',
             'schedules.date',
@@ -772,7 +772,6 @@ class DashboardService
             'schedules.address_id',
             'schedules.status',
             'custom_schedules.offers_meal',
-            'client_media.path as customer_photo_path',
 
             DB::raw("
                 CASE
@@ -801,12 +800,6 @@ class DashboardService
         ->get();
 
         $solicitations->each(function ($solicitation) use ($address) {
-            $solicitation->customer_photo = $solicitation->customer_photo_path
-                ? Storage::temporaryUrl($solicitation->customer_photo_path, now()->addMinutes(60))
-                : null;
-
-            unset($solicitation->customer_photo_path);
-
             $solicitation->distance_km = $this->zipCodeCoordinatesService->calculateDistance(
                 $address?->latitude !== null ? (float) $address->latitude : null,
                 $address?->longitude !== null ? (float) $address->longitude : null,
@@ -825,7 +818,6 @@ class DashboardService
         ->whereDate('schedules.date', now()->toDateString())
         ->leftJoin('clients', 'clients.id', '=', 'schedules.client_id')
         ->leftJoin('users as client_user', 'client_user.id', '=', 'clients.user_id')
-        ->leftJoin('media as client_media', 'client_media.id', '=', 'clients.profile_media_id')
         ->leftJoin('custom_schedules', 'custom_schedules.schedule_id', '=', 'schedules.id')
         ->select(
             'schedules.id',
@@ -842,7 +834,6 @@ class DashboardService
             'schedules.code_verified',
             'schedules.code',
             'custom_schedules.offers_meal',
-            'client_media.path as customer_photo_path',
 
             DB::raw("
                 EXISTS (
@@ -858,24 +849,16 @@ class DashboardService
         ->orderBy('schedules.start_time', 'asc')
         ->get();
 
-        $todayServices->each(function ($s) {
-            $s->customer_photo = $s->customer_photo_path
-                ? Storage::temporaryUrl($s->customer_photo_path, now()->addMinutes(60))
-                : null;
-
-            unset($s->customer_photo_path);
-        });
-
         $nextSchedules = Schedule::with('address:district,address,number,source_id,source,id,zip_code,latitude,longitude')
             ->where('schedules.provider_id', $provider->id)
             ->whereIn('schedules.status', ['accepted', 'paid'])
             ->whereDate('schedules.date', '>=', now()->toDateString())
             ->leftJoin('clients', 'clients.id', '=', 'schedules.client_id')
             ->leftJoin('users as client_user', 'client_user.id', '=', 'clients.user_id')
-            ->leftJoin('media as client_media', 'client_media.id', '=', 'clients.profile_media_id')
             ->leftJoin('custom_schedules', 'custom_schedules.schedule_id', '=', 'schedules.id')
             ->select(
                 'schedules.id',
+                'schedules.client_id',
                 'client_user.name as client_name',
                 'schedules.date',
                 'schedules.start_time',
@@ -886,18 +869,11 @@ class DashboardService
                 'schedules.schedule_type',
                 'schedules.status',
                 'custom_schedules.offers_meal',
-                'client_media.path as customer_photo_path',
             )
             ->orderBy('schedules.date', 'asc')
             ->get();
 
         $nextSchedules->each(function ($schedule) use ($address) {
-            $schedule->customer_photo = $schedule->customer_photo_path
-                ? Storage::temporaryUrl($schedule->customer_photo_path, now()->addMinutes(60))
-                : null;
-
-            unset($schedule->customer_photo_path);
-
             $schedule->distance_km = $this->zipCodeCoordinatesService->calculateDistance(
                 $address?->latitude !== null ? (float) $address->latitude : null,
                 $address?->longitude !== null ? (float) $address->longitude : null,
@@ -908,6 +884,24 @@ class DashboardService
             );
         });
 
+        $clientCollections = collect([
+            $solicitations,
+            $todayServices,
+            $nextSchedules,
+        ]);
+
+        $clientPhotoUrls = $this->clientPhotoUrls(
+            $clientCollections->flatMap(
+                fn (Collection $items) => $items->pluck('client_id'),
+            ),
+        );
+
+        $clientCollections->each(function (Collection $items) use ($clientPhotoUrls) {
+            $items->each(function ($item) use ($clientPhotoUrls) {
+                $item->customer_photo = $clientPhotoUrls->get($item->client_id);
+            });
+        });
+
         $notifications = Notification::where('user_id', $user->id)
             ->orderBy('read', 'asc')
             ->orderBy('created_at', 'desc')
@@ -1012,6 +1006,24 @@ class DashboardService
             });
     }
 
+    // Gera URLs apenas para fotos verificadas ou visíveis ao próprio cliente.
+
+    private function clientPhotoUrls(iterable $clientIds): Collection
+    {
+        return Client::query()
+            ->select('id', 'profile_media_id')
+            ->with('profileMedia')
+            ->whereIn('id', collect($clientIds)->filter()->unique())
+            ->get()
+            ->mapWithKeys(function (Client $client) {
+                $path = $client->profileMedia?->path;
+
+                return [
+                    $client->id => $path ? Storage::temporaryUrl($path, now()->addMinutes(60)) : null,
+                ];
+            });
+    }
+
     //
 
     private function applyCreditCardFee(?float $price): ?float

+ 22 - 0
database/migrations/2026_07_22_000001_add_selfie_verified_to_clients_table.php

@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('clients', function (Blueprint $table) {
+            $table->boolean('selfie_verified')->default(false)->after('profile_media_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('clients', function (Blueprint $table) {
+            $table->dropColumn('selfie_verified');
+        });
+    }
+};