Skip to content
Snippets Groups Projects
Verified Commit 6f87f0d0 authored by Sofiane Lasri's avatar Sofiane Lasri
Browse files

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	app/Http/Controllers/Admin/HomeController.php
parents 4d6a3312 5a4b8c3d
Branches
No related tags found
No related merge requests found
Showing
with 513 additions and 53 deletions
...@@ -78,3 +78,12 @@ BUNNYCDN_API_KEY="api-key" ...@@ -78,3 +78,12 @@ BUNNYCDN_API_KEY="api-key"
BUNNYCDN_REGION=de BUNNYCDN_REGION=de
CDN_FILESYSTEM_DISK=bunnycdn CDN_FILESYSTEM_DISK=bunnycdn
AI_PROVIDER=openai
OPENAI_URL=https://api.openai.com/v1/chat/completions
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini
IP_ADDRESS_RESOLVER_URL=http://ip-api.com/batch
IP_ADDRESS_RESOLVER_CALL_LIMIT_PER_MINUTE=15
IP_ADDRESS_RESOLVER_MAX_IP_ADDRESSES_PER_CALL=100
\ No newline at end of file
...@@ -78,3 +78,14 @@ BUNNYCDN_API_KEY="api-key" ...@@ -78,3 +78,14 @@ BUNNYCDN_API_KEY="api-key"
BUNNYCDN_REGION=de BUNNYCDN_REGION=de
CDN_FILESYSTEM_DISK=bunnycdn CDN_FILESYSTEM_DISK=bunnycdn
AI_PROVIDER=openai
OPENAI_URL=https://api.openai.com/v1/chat/completions
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini
IP_ADDRESS_RESOLVER_URL=http://ip-api.com/batch
IP_ADDRESS_RESOLVER_CALL_LIMIT_PER_MINUTE=15
IP_ADDRESS_RESOLVER_MAX_IP_ADDRESSES_PER_CALL=100
APP_PRIVATE_MODE_SECRET=secret
\ No newline at end of file
...@@ -74,3 +74,14 @@ BUNNYCDN_STORAGE_ZONE=testing_storage_zone ...@@ -74,3 +74,14 @@ BUNNYCDN_STORAGE_ZONE=testing_storage_zone
BUNNYCDN_PULL_ZONE=https://testing.b-cdn.net BUNNYCDN_PULL_ZONE=https://testing.b-cdn.net
BUNNYCDN_API_KEY="api-key" BUNNYCDN_API_KEY="api-key"
BUNNYCDN_REGION=de BUNNYCDN_REGION=de
AI_PROVIDER=openai
OPENAI_URL=https://api.test-provider.com/v1/chat/completions
OPENAI_API_KEY=fake-api-key
OPENAI_MODEL=gpt-4o-mini
IP_ADDRESS_RESOLVER_URL=http://api.test-provider.com/batch
IP_ADDRESS_RESOLVER_CALL_LIMIT_PER_MINUTE=15
IP_ADDRESS_RESOLVER_MAX_IP_ADDRESSES_PER_CALL=100
APP_PRIVATE_MODE_SECRET=secret
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<component name="LaravelPint"> <component name="LaravelPint">
<laravel_pint_settings> <laravel_pint_settings>
<LaravelPintConfiguration tool_path="$PROJECT_DIR$/vendor/bin/pint" /> <LaravelPintConfiguration tool_path="$PROJECT_DIR$/vendor/bin/pint" />
<laravel_pint_by_interpreter asDefaultInterpreter="true" interpreter_id="41eb493c-2ba1-4c74-b9ea-41d9bb4c70c9" tool_path="/var/www/vendor/bin/pint"> <laravel_pint_by_interpreter asDefaultInterpreter="true" interpreter_id="41eb493c-2ba1-4c74-b9ea-41d9bb4c70c9" tool_path="/app/vendor/bin/pint">
<option name="timeout" value="30000" /> <option name="timeout" value="30000" />
</laravel_pint_by_interpreter> </laravel_pint_by_interpreter>
</laravel_pint_settings> </laravel_pint_settings>
...@@ -176,6 +176,9 @@ ...@@ -176,6 +176,9 @@
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" /> <path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/laminas/laminas-diactoros" /> <path value="$PROJECT_DIR$/vendor/laminas/laminas-diactoros" />
<path value="$PROJECT_DIR$/vendor/laravel/octane" /> <path value="$PROJECT_DIR$/vendor/laravel/octane" />
<path value="$PROJECT_DIR$/vendor/_laravel_idea" />
<path value="$PROJECT_DIR$/vendor/mkocansey/bladewind" />
<path value="$PROJECT_DIR$/vendor/laravel/horizon" />
</include_path> </include_path>
</component> </component>
<component name="PhpInterpreters"> <component name="PhpInterpreters">
......
...@@ -20,5 +20,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh && \ ...@@ -20,5 +20,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh && \
bash nodesource_setup.sh && \ bash nodesource_setup.sh && \
apt-get install -y nodejs apt-get install -y nodejs
# Install supervisor
RUN apt-get install -y supervisor
COPY docker-init/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Setting PHP Configuration # Setting PHP Configuration
COPY docker-init/php.ini $PHP_INI_DIR/php.ini COPY docker-init/php.ini $PHP_INI_DIR/php.ini
\ No newline at end of file
...@@ -62,25 +62,6 @@ #### Laravel Idea ...@@ -62,25 +62,6 @@ #### Laravel Idea
#### Configuration #### Configuration
##### Configuration des projets multiples
Comme le projet est dépendant de quelques paquets développés en interne, il est nécessaire de configurer PHPStorm pour
vous faciliter leur développement en simultané.
1. Ouvrez les préférences de PHPStorm (Ctrl + Alt + S)
2. Allez dans `Version Control`
3. Allez dans `Directory Mappings`
4. Cliquez sur `+` puis `Directory`
5. Sélectionnez le dossier du projet sous-jacent
6. Cliquez sur `OK`
![Projets Git Multiples](a1readme-assets/multi-vcs.jpg)
Vous devriez voir apparaître les multiples projets Git dans l'onglet vcs de PHPStorm. À présent, vous devriez pouvoir
créer des branches sur les différents projets simultanément.
![Menu VCS](a1readme-assets/vcs-menu.jpg)
##### Interpréteur PHP ##### Interpréteur PHP
Il est nécessaire de renseigner le container Docker comme interpréteur PHP. Pour cela, suivez les étapes suivantes : Il est nécessaire de renseigner le container Docker comme interpréteur PHP. Pour cela, suivez les étapes suivantes :
......
<?php
namespace App\Console\Commands;
use App\Jobs\ProcessIpAddressesJob;
use Illuminate\Console\Command;
use SlProjects\LaravelRequestLogger\app\Models\IpAddress;
class ProcessIpAdressesCommand extends Command
{
protected $signature = 'process:ip-adresses';
protected $description = 'Process ip adresses to resolve their location';
public function handle(): void
{
$ipAddresses = IpAddress::leftJoin('ip_address_metadata', 'ip_addresses.id', '=', 'ip_address_metadata.ip_address_id')
->whereNull('ip_address_metadata.id')
->select('ip_addresses.id', 'ip_addresses.ip')
->get();
if ($ipAddresses->isEmpty()) {
$this->info('No ip adresses to process');
return;
}
$this->info("Processing {$ipAddresses->count()} ip adresses");
ProcessIpAddressesJob::dispatch($ipAddresses);
}
}
<?php
namespace App\Console\Commands;
use App\Jobs\ProcessUserAgentJob;
use Illuminate\Console\Command;
use SlProjects\LaravelRequestLogger\app\Models\UserAgent;
class ProcessUserAgentsCommand extends Command
{
protected $signature = 'process:user-agents';
protected $description = 'Process user agents to detect if they are bots';
public function handle(): void
{
$userAgents = UserAgent::leftJoin('user_agent_metadata', 'user_agents.id', '=', 'user_agent_metadata.user_agent_id')
->whereNull('user_agent_metadata.id')
->select('user_agents.id', 'user_agents.user_agent')
->get();
if ($userAgents->isEmpty()) {
$this->info('No user agents to process.');
return;
}
foreach ($userAgents as $userAgent) {
ProcessUserAgentJob::dispatch($userAgent);
}
$this->info('Jobs dispatched for processing '.$userAgents->count().' user agents.');
}
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Jobs\TranslateCreationJob;
use App\Models\Category; use App\Models\Category;
use App\Models\Creation; use App\Models\Creation;
use App\Models\Translation; use App\Models\Translation;
...@@ -350,4 +351,15 @@ public function removeAdditionalImage(int $creationId, int $uploadedPictureId): ...@@ -350,4 +351,15 @@ public function removeAdditionalImage(int $creationId, int $uploadedPictureId):
'success' => true, 'success' => true,
]); ]);
} }
public function translateWithAi(int $creationId): JsonResponse
{
$creation = Creation::findOrFail($creationId);
$job = new TranslateCreationJob($creation);
dispatch($job);
return response()->json([
'success' => true,
]);
}
} }
...@@ -19,24 +19,30 @@ public function index(Request $request): View ...@@ -19,24 +19,30 @@ public function index(Request $request): View
'end_date' => 'nullable|date', 'end_date' => 'nullable|date',
]); ]);
$publicRoutes = collect(Route::getRoutes()->getRoutes()) $routes = Route::getRoutes()->getRoutes();
->filter(fn ($route) => ! Str::startsWith($route->uri, ['admin', 'api']) && ! empty($route->uri) && $route->uri !== '/')
->pluck('uri'); $individualExcludedRoutes = [];
foreach ($routes as $route) {
$visits = LoggedRequest::selectRaw(' if (Str::startsWith($route->uri, ['login', 'register', 'password', 'admin', 'forgot-password'])) {
logged_requests.url_id, $individualExcludedRoutes[] = config('app.url').'/'.$route->uri;
logged_requests.ip_address_id, }
logged_requests.country_code, }
logged_requests.created_at,
urls.url $visits = LoggedRequest::select([
') 'logged_requests.url_id',
'logged_requests.ip_address_id',
'ip_address_metadata.country_code',
'logged_requests.created_at',
'urls.url'])
->distinct('logged_requests.url_id', 'logged_requests.ip_address_id') ->distinct('logged_requests.url_id', 'logged_requests.ip_address_id')
->join('urls', 'logged_requests.url_id', '=', 'urls.id') ->join('urls', 'logged_requests.url_id', '=', 'urls.id')
->where(function ($query) use ($publicRoutes) { ->join('user_agent_metadata', 'logged_requests.user_agent_id', '=', 'user_agent_metadata.user_agent_id')
foreach ($publicRoutes as $uri) { ->join('ip_address_metadata', 'logged_requests.ip_address_id', '=', 'ip_address_metadata.ip_address_id')
$query->orWhere('urls.url', 'like', "%$uri%"); ->whereLike('urls.url', config('app.url').'%')
} ->whereNotIn('urls.url', $individualExcludedRoutes)
}) ->where('user_agent_metadata.is_bot', false)
->where('status_code', 200)
->whereNull('logged_requests.user_id')
->get(); ->get();
$now = now(); $now = now();
...@@ -60,29 +66,18 @@ public function index(Request $request): View ...@@ -60,29 +66,18 @@ public function index(Request $request): View
$selectedPeriod = $startDate; $selectedPeriod = $startDate;
// Now, all the stats are calculated for the selected period // Now, all the stats are calculated for the selected period
/*$visitsPerDay = $visits->groupBy(fn ($visit) => Carbon::parse($visit->created_at)->format('Y-m-d'))
->map(fn ($group) => ['date' => $group->first()->created_at->format('Y-m-d'), 'count' => $group->count()])
->values();*/
$visitsPerDay = $visits->where('created_at', '>=', $startDate) $visitsPerDay = $visits->where('created_at', '>=', $startDate)
->where('created_at', '<=', $dateEnd) ->where('created_at', '<=', $dateEnd)
->groupBy(fn ($visit) => Carbon::parse($visit->created_at)->format('Y-m-d')) ->groupBy(fn ($visit) => Carbon::parse($visit->created_at)->format('Y-m-d'))
->map(fn ($group) => ['date' => $group->first()->created_at->format('Y-m-d'), 'count' => $group->count()]) ->map(fn ($group) => ['date' => $group->first()->created_at->format('Y-m-d'), 'count' => $group->count()])
->values(); ->values();
/*$visitsByCountry = $visits->groupBy('country_code')
->map(fn ($group, $country) => ['country_code' => $country, 'count' => $group->count()])
->values();*/
$visitsByCountry = $visits->where('created_at', '>=', $startDate) $visitsByCountry = $visits->where('created_at', '>=', $startDate)
->where('created_at', '<=', $dateEnd) ->where('created_at', '<=', $dateEnd)
->groupBy('country_code') ->groupBy('country_code')
->map(fn ($group, $country) => ['country_code' => $country, 'count' => $group->count()]) ->map(fn ($group, $country) => ['country_code' => $country, 'count' => $group->count()])
->values(); ->values();
/*$mostVisitedPages = $visits->groupBy('url')
->map(fn ($group, $url) => ['url' => $url, 'count' => $group->count()])
->sortByDesc('count')
->values();*/
$mostVisitedPages = $visits->where('created_at', '>=', $startDate) $mostVisitedPages = $visits->where('created_at', '>=', $startDate)
->where('created_at', '<=', $dateEnd) ->where('created_at', '<=', $dateEnd)
->groupBy('url') ->groupBy('url')
...@@ -90,6 +85,24 @@ public function index(Request $request): View ...@@ -90,6 +85,24 @@ public function index(Request $request): View
->sortByDesc('count') ->sortByDesc('count')
->values(); ->values();
$mostVisitedPagesForPastTwentyFourHours = $visits->where('created_at', '>=', $now->copy()->subDay())
->groupBy('url')
->map(fn ($group, $url) => ['url' => $url, 'count' => $group->count()])
->sortByDesc('count')
->values();
$mostVisitedPagesForPastSevenDays = $visits->where('created_at', '>=', $now->copy()->subDays(7))
->groupBy('url')
->map(fn ($group, $url) => ['url' => $url, 'count' => $group->count()])
->sortByDesc('count')
->values();
$mostVisitedPagesForPastThirtyDays = $visits->where('created_at', '>=', $now->copy()->subDays(30))
->groupBy('url')
->map(fn ($group, $url) => ['url' => $url, 'count' => $group->count()])
->sortByDesc('count')
->values();
return view('admin.home', [ return view('admin.home', [
'totalVisitsPastTwentyFourHours' => $totalVisitsPastTwentyFourHours, 'totalVisitsPastTwentyFourHours' => $totalVisitsPastTwentyFourHours,
'totalVisitsPastSevenDays' => $totalVisitsPastSevenDays, 'totalVisitsPastSevenDays' => $totalVisitsPastSevenDays,
...@@ -100,6 +113,9 @@ public function index(Request $request): View ...@@ -100,6 +113,9 @@ public function index(Request $request): View
'mostVisitedPages' => $mostVisitedPages, 'mostVisitedPages' => $mostVisitedPages,
'periods' => $periods, 'periods' => $periods,
'selectedPeriod' => $selectedPeriod, 'selectedPeriod' => $selectedPeriod,
'mostVisitedPagesForPastTwentyFourHours' => $mostVisitedPagesForPastTwentyFourHours,
'mostVisitedPagesForPastSevenDays' => $mostVisitedPagesForPastSevenDays,
'mostVisitedPagesForPastThirtyDays' => $mostVisitedPagesForPastThirtyDays,
]); ]);
} }
} }
<?php
namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller;
use App\Models\LegalMention;
class LegalMentionsController extends Controller
{
public function __invoke()
{
$activeLegalMentionsText = LegalMention::where('active', true)->first();
if (! $activeLegalMentionsText) {
abort(404);
}
$activeLegalMentionsText = $activeLegalMentionsText->contentTransKey->getTranslation();
return view('public.legal-mentions', compact('activeLegalMentionsText'));
}
}
<?php
namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller;
use App\Models\TermsSection;
use Illuminate\View\View;
class TermsController extends Controller
{
public function __invoke(): View
{
$activeTermsText = TermsSection::where('active', true)->first();
if (! $activeTermsText) {
abort(404);
}
$activeTermsText = $activeTermsText->contentTranslationKey->getTranslation();
return view('public.terms', compact('activeTermsText'));
}
}
...@@ -9,9 +9,20 @@ class CheckPrivateModeMiddleware ...@@ -9,9 +9,20 @@ class CheckPrivateModeMiddleware
{ {
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (config('app.private_mode') && ! $request->is('maintenance') && ! auth()->check()) { $privateModeEnabled = config('app.private_mode');
$privateModeSecret = config('app.private_mode_secret');
$userSecretInput = $request->input('secret');
$secretIsUsable = ! empty($privateModeSecret) && $privateModeSecret === $userSecretInput;
if ($privateModeEnabled) {
if (! $secretIsUsable && ! auth()->check()) {
if (! $request->is('maintenance')) {
return redirect()->route('maintenance'); return redirect()->route('maintenance');
} elseif (! config('app.private_mode') && $request->is('maintenance')) { }
}
}
if (! $privateModeEnabled && $request->is('maintenance') && ! auth()->check()) {
return redirect()->route('index'); return redirect()->route('index');
} }
......
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserAgentMetadataRequest extends FormRequest
{
public function rules(): array
{
return [
'user_agent_id' => ['required', 'exists:user_agents'],
'is_bot' => ['boolean'],
];
}
public function authorize(): bool
{
return true;
}
}
<?php
namespace App\Jobs;
use App\Models\IpAddressMetadata;
use App\Services\IpAddressMetadataResolverService;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ProcessIpAddressesJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private readonly Collection $ipAddresses) {}
public function handle(IpAddressMetadataResolverService $ipAddressMetadataResolver): void
{
try {
$metadataObjects = $ipAddressMetadataResolver->resolve($this->ipAddresses);
} catch (Exception $exception) {
report($exception);
$this->fail($exception);
return;
}
foreach ($metadataObjects as $metadata) {
if ($metadata['status'] === 'fail') {
Log::warning('Failed to resolve IP address metadata.', [
'query' => $metadata['query'],
'message' => $metadata['message'],
]);
continue;
}
IpAddressMetadata::create([
'ip_address_id' => $this->ipAddresses->where('ip', $metadata['query'])->first()->id,
'country_code' => $metadata['countryCode'],
'lat' => $metadata['lat'],
'lon' => $metadata['lon'],
]);
}
}
}
<?php
namespace App\Jobs;
use App\Models\UserAgentMetadata;
use App\Services\AiProviderService;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use SlProjects\LaravelRequestLogger\app\Models\UserAgent;
class ProcessUserAgentJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private readonly UserAgent $userAgent) {}
public function handle(AiProviderService $aiProviderService): void
{
try {
$result = $aiProviderService->prompt(
'You are a robot detector designed to output JSON. ',
"Is this user agent a robot or a tool that is not a web browser? Please respond in the format {'is_bot': true/false}. The user agent is: {$this->userAgent->user_agent}"
);
$isBot = $result['is_bot'] ?? false;
UserAgentMetadata::create([
'user_agent_id' => $this->userAgent->id,
'is_bot' => $isBot,
]);
} catch (Exception $e) {
Log::error("Failed to process UserAgent: {$this->userAgent->user_agent}", [
'exception' => $e->getMessage(),
]);
$this->fail($e);
}
}
}
<?php
namespace App\Jobs;
use App\Models\Creation;
use App\Models\Translation;
use App\Services\AiProviderService;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class TranslateCreationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private readonly Creation $creation) {}
public function handle(AiProviderService $aiProviderService): void
{
$frenchName = $this->creation->nameTranslationKey->getTranslation('fr');
$frenchShortDesc = $this->creation->shortDescriptionTranslationKey->getTranslation('fr');
$frenchDesc = $this->creation->descriptionTranslationKey->getTranslation('fr');
$shortDescriptionTranslationKeyId = $this->creation->shortDescriptionTranslationKey->id;
$descriptionTranslationKeyId = $this->creation->descriptionTranslationKey->id;
if (! empty($frenchName)) {
Translation::updateOrCreate(
['translation_key_id' => $this->creation->name_translation_key_id, 'locale' => 'en'],
['text' => $frenchName]
);
Cache::forget("translation_key_{$this->creation->name_translation_key_id}_en");
}
if (! empty($frenchShortDesc)) {
Translation::updateOrCreate(
['translation_key_id' => $shortDescriptionTranslationKeyId, 'locale' => 'en'],
['text' => $this->translate($frenchShortDesc, $aiProviderService)]
);
Cache::forget("translation_key_{$shortDescriptionTranslationKeyId}_en");
}
if (! empty($frenchDesc)) {
Translation::updateOrCreate(
['translation_key_id' => $descriptionTranslationKeyId, 'locale' => 'en'],
['text' => $this->translate($frenchDesc, $aiProviderService)]
);
Cache::forget("translation_key_{$descriptionTranslationKeyId}_en");
}
}
/**
* Translate the given text from French to English
*
* @param string $text The text to translate
* @param AiProviderService $aiProviderService The AI provider service
* @return string The translated text
*/
private function translate(string $text, AiProviderService $aiProviderService): string
{
try {
$result = $aiProviderService->prompt(
'You are a helpful assistant that translates french markdown text in english and that outputs JSON in the format {message:string}. Markdown is supported.',
$text
);
return $result['message'] ?? '';
} catch (Exception $e) {
Log::error("Failed to translate text: {$text}", [
'exception' => $e->getMessage(),
]);
}
return '';
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use SlProjects\LaravelRequestLogger\app\Models\IpAddress;
class IpAddressMetadata extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = [
'ip_address_id',
'country_code',
'lat',
'lon',
];
const COUNTRY_CODES = ['AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ', 'BA', 'BB', 'BC', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BK', 'BL', 'BM', 'BN', 'BO', 'BP', 'BQ', 'BR', 'BS', 'BT', 'BU', 'BV', 'BW', 'BX', 'BY', 'BZ', 'CA', 'CB', 'CC', 'CD', 'CE', 'CF', 'CG', 'CH', 'CI', 'CJ', 'CK', 'CL', 'CM', 'CN', 'CO', 'CP', 'CQ', 'CR', 'CS', 'CT', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DA', 'DB', 'DC', 'DD', 'DE', 'DF', 'DG', 'DH', 'DI', 'DJ', 'DK', 'DL', 'DM', 'DN', 'DO', 'DP', 'DQ', 'DR', 'DS', 'DT', 'DU', 'DV', 'DW', 'DX', 'DY', 'DZ', 'EA', 'EB', 'EC', 'ED', 'EE', 'EF', 'EG', 'EH', 'EI', 'EJ', 'EK', 'EL', 'EM', 'EN', 'EO', 'EP', 'EQ', 'ER', 'ES', 'ET', 'EU', 'EV', 'EW', 'EX', 'EY', 'EZ', 'FA', 'FB', 'FC', 'FD', 'FE', 'FF', 'FG', 'FH', 'FI', 'FJ', 'FK', 'FL', 'FM', 'FN', 'FO', 'FP', 'FQ', 'FR', 'FS', 'FT', 'FU', 'FV', 'FW', 'FX', 'FY', 'FZ', 'GA', 'GB', 'GC', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GJ', 'GK', 'GL', 'GM', 'GN', 'GO', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GV', 'GW', 'GX', 'GY', 'GZ', 'HA', 'HB', 'HC', 'HD', 'HE', 'HF', 'HG', 'HH', 'HI', 'HJ', 'HK', 'HL', 'HM', 'HN', 'HO', 'HP', 'HQ', 'HR', 'HS', 'HT', 'HU', 'HV', 'HW', 'HX', 'HY', 'HZ', 'IA', 'IB', 'IC', 'ID', 'IE', 'IF', 'IG', 'IH', 'II', 'IJ', 'IK', 'IL', 'IM', 'IN', 'IO', 'IP', 'IQ', 'IR', 'IS', 'IT', 'IU', 'IV', 'IW', 'IX', 'IY', 'IZ', 'JA', 'JB', 'JC', 'JD', 'JE', 'JF', 'JG', 'JH', 'JI', 'JJ', 'JK', 'JL', 'JM', 'JN', 'JO', 'JP', 'JQ', 'JR', 'JS', 'JT', 'JU', 'JV', 'JW', 'JX', 'JY', 'JZ', 'KA', 'KB', 'KC', 'KD', 'KE', 'KF', 'KG', 'KH', 'KI', 'KJ', 'KK', 'KL', 'KM', 'KN', 'KO', 'KP', 'KQ', 'KR', 'KS', 'KT', 'KU', 'KV', 'KW', 'KX', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LD', 'LE', 'LF', 'LG', 'LH', 'LI', 'LJ', 'LK', 'LL', 'LM', 'LN', 'LO', 'LP', 'LQ', 'LR', 'LS', 'LT', 'LU', 'LV', 'LW', 'LX', 'LY', 'LZ', 'MA', 'MB', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MI', 'MJ', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NB', 'NC', 'ND', 'NE', 'NF', 'NG', 'NH', 'NI', 'NJ', 'NK', 'NL', 'NM', 'NN', 'NO', 'NP', 'NQ', 'NR', 'NS', 'NT', 'NU', 'NV', 'NW', 'NX', 'NY', 'NZ', 'OA', 'OB', 'OC', 'OD', 'OE', 'OF', 'OG', 'OH', 'OI', 'OJ', 'OK', 'OL', 'OM', 'ON', 'OO', 'OP', 'OQ', 'OR', 'OS', 'OT', 'OU', 'OV', 'OW', 'OX', 'OY', 'OZ', 'PA', 'PB', 'PC', 'PD', 'PE', 'PF', 'PG', 'PH', 'PI', 'PJ', 'PK', 'PL', 'PM', 'PN', 'PO', 'PP', 'PQ', 'PR', 'PS', 'PT', 'PU', 'PV', 'PW', 'PX', 'PY', 'PZ', 'QA', 'QB', 'QC', 'QD', 'QE', 'QF', 'QG', 'QH', 'QI', 'QJ', 'QK', 'QL', 'QM', 'QN', 'QO', 'QP', 'QQ', 'QR', 'QS', 'QT', 'QU', 'QV', 'QW', 'QX', 'QY', 'QZ', 'RA', 'RB', 'RC', 'RD', 'RE', 'RF', 'RG', 'RH', 'RI', 'RJ', 'RK', 'RL', 'RM', 'RN', 'RO', 'RP', 'RQ', 'RR', 'RS', 'RT', 'RU', 'RV', 'RW', 'RX', 'RY', 'RZ', 'SA', 'SB', 'SC', 'SD', 'SE', 'SF', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SP', 'SQ', 'SR', 'SS', 'ST', 'SU', 'SV', 'SW', 'SX', 'SY', 'SZ', 'TA', 'TB', 'TC', 'TD', 'TE', 'TF', 'TG', 'TH', 'TI', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TP', 'TQ', 'TR', 'TS', 'TT', 'TU', 'TV', 'TW', 'TX', 'TY', 'TZ', 'UA', 'UB', 'UC', 'UD', 'UE', 'UF', 'UG', 'UH', 'UI', 'UJ', 'UK', 'UL', 'UM', 'UN', 'UO', 'UP', 'UQ', 'UR', 'US', 'UT', 'UU', 'UV', 'UW', 'UX', 'UY', 'UZ', 'VA', 'VB', 'VC', 'VD', 'VE', 'VF', 'VG', 'VH', 'VI', 'VJ', 'VK', 'VL', 'VM', 'VN', 'VO', 'VP', 'VQ', 'VR', 'VS', 'VT', 'VU', 'VV', 'VW', 'VX', 'VY', 'VZ', 'WA', 'WB', 'WC', 'WD', 'WE', 'WF', 'WG', 'WH', 'WI', 'WJ', 'WK', 'WL', 'WM', 'WN', 'WO', 'WP', 'WQ', 'WR', 'WS', 'WT', 'WU', 'WV', 'WW', 'WX', 'WY', 'WZ', 'XA', 'XB', 'XC', 'XD', 'XE', 'XF', 'XG', 'XH', 'XI', 'XJ', 'XK', 'XL', 'XM', 'XN', 'XO', 'XP', 'XQ', 'XR', 'XS', 'XT', 'XU', 'XV', 'XW', 'XX', 'XY', 'XZ', 'YA', 'YB', 'YC', 'YD', 'YE', 'YF', 'YG', 'YH', 'YI', 'YJ', 'YK', 'YL', 'YM', 'YN', 'YO', 'YP', 'YQ', 'YR', 'YS', 'YT', 'YU', 'YV', 'YW', 'YX', 'YY', 'YZ', 'ZA', 'ZB', 'ZC', 'ZD', 'ZE', 'ZF', 'ZG', 'ZH', 'ZI', 'ZJ', 'ZK', 'ZL', 'ZM', 'ZN', 'ZO', 'ZP', 'ZQ', 'ZR', 'ZS', 'ZT', 'ZU', 'ZV', 'ZW', 'ZX', 'ZY', 'ZZ'];
public function ipAddress(): BelongsTo
{
return $this->belongsTo(IpAddress::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use SlProjects\LaravelRequestLogger\app\Models\UserAgent;
class UserAgentMetadata extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = [
'user_agent_id',
'is_bot',
];
public function userAgent(): BelongsTo
{
return $this->belongsTo(UserAgent::class);
}
protected function casts(): array
{
return [
'is_bot' => 'boolean',
];
}
}
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\HorizonApplicationServiceProvider;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
parent::boot();
// Horizon::routeSmsNotificationsTo('15556667777');
// Horizon::routeMailNotificationsTo('example@example.com');
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
}
/**
* Register the Horizon gate.
*
* This gate determines who can access Horizon in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return in_array($user->email, [
//
]);
});
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment