Repo rebuild

This commit is contained in:
Mark Gregory
2026-09-15 12:33:28 +01:00
commit be28c4feec
5629 changed files with 317074 additions and 0 deletions
@@ -0,0 +1,94 @@
<?php
namespace Modules\Countries\Http\Controllers\Api;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Countries\Models\Country;
use Modules\Countries\Models\CountrySubdivision;
use Modules\Countries\Support\CountryRepository;
class CountriesApiController extends Controller
{
/**
* List ISO 3166-1 countries for integrations and selectors.
*/
public function index(Request $request): JsonResponse
{
$activeOnly = $request->boolean('active_only', false);
$search = trim((string) $request->query('search', ''));
$countries = Country::query()
->when($activeOnly, fn ($query) => $query->where('is_active', true))
->when($search !== '', function ($query) use ($search): void {
$query->where(function ($nested) use ($search): void {
$nested->where('name', 'ilike', "%{$search}%")
->orWhere('official_name', 'ilike', "%{$search}%")
->orWhere('common_name', 'ilike', "%{$search}%")
->orWhere('display_name', 'ilike', "%{$search}%")
->orWhere('native_name', 'ilike', "%{$search}%")
->orWhere('alpha2', 'ilike', "%{$search}%")
->orWhere('alpha3', 'ilike', "%{$search}%")
->orWhere('numeric', 'ilike', "%{$search}%")
->orWhere('region', 'ilike', "%{$search}%")
->orWhere('subregion', 'ilike', "%{$search}%")
->orWhere('currency_code', 'ilike', "%{$search}%")
->orWhere('calling_code', 'ilike', "%{$search}%");
});
})
->ordered()
->get();
return response()->json(['data' => $countries]);
}
/**
* List active ISO 3166-1 countries for business selectors.
*/
public function active(): JsonResponse
{
$countries = Country::query()->active()->ordered()->get();
return response()->json(['data' => $countries]);
}
/**
* Show one country by alpha-2 route binding.
*/
public function show(Country $country): JsonResponse
{
return response()->json(['data' => $country->loadCount('subdivisions')]);
}
/**
* Resolve alpha-2, alpha-3 or numeric ISO 3166-1 country code.
*/
public function resolve(string $code, CountryRepository $countries): JsonResponse
{
$country = $countries->findByIsoCode($code);
abort_if($country === null, 404, 'Country code not found.');
return response()->json(['data' => $country]);
}
/**
* List ISO 3166-2 subdivisions for the supplied ISO 3166-1 alpha-2 country.
*/
public function subdivisions(string $alpha2, Request $request, CountryRepository $countries): JsonResponse
{
$activeOnly = $request->boolean('active_only', true);
$country = $countries->findByAlpha2($alpha2);
abort_if($country === null, 404, 'Country not found.');
$subdivisions = CountrySubdivision::query()
->where('country_id', $country->id)
->when($activeOnly, fn ($query) => $query->where('is_active', true))
->ordered()
->get();
return response()->json(['data' => $subdivisions]);
}
}
@@ -0,0 +1,140 @@
<?php
namespace Modules\Countries\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Countries\Http\Requests\StoreCountryRequest;
use Modules\Countries\Http\Requests\UpdateCountryRequest;
use Modules\Countries\Models\Country;
use Modules\Countries\Support\CountryUsageReport;
class CountriesController extends Controller
{
public function __invoke(Request $request): View
{
$search = trim((string) $request->query('search', ''));
$status = (string) $request->query('status', 'all');
$region = (string) $request->query('region', 'all');
$countries = Country::query()
->when($search !== '', function ($query) use ($search): void {
$query->where(function ($nested) use ($search): void {
$nested->where('name', 'ilike', "%{$search}%")
->orWhere('official_name', 'ilike', "%{$search}%")
->orWhere('common_name', 'ilike', "%{$search}%")
->orWhere('display_name', 'ilike', "%{$search}%")
->orWhere('native_name', 'ilike', "%{$search}%")
->orWhere('alpha2', 'ilike', "%{$search}%")
->orWhere('alpha3', 'ilike', "%{$search}%")
->orWhere('numeric', 'ilike', "%{$search}%")
->orWhere('currency_code', 'ilike', "%{$search}%")
->orWhere('calling_code', 'ilike', "%{$search}%");
});
})
->when($status === 'active', fn ($query) => $query->where('is_active', true))
->when($status === 'inactive', fn ($query) => $query->where('is_active', false))
->when($region !== 'all' && $region !== '', fn ($query) => $query->where('region', $region))
->orderByRaw('LOWER(COALESCE(display_name, common_name, name)) ASC')
->paginate(50)
->withQueryString();
$activeCount = Country::query()->where('is_active', true)->count();
$inactiveCount = Country::query()->where('is_active', false)->count();
$totalCount = Country::query()->count();
$regions = Country::query()
->whereNotNull('region')
->distinct()
->orderBy('region')
->pluck('region');
return view('countries::index', compact(
'countries',
'activeCount',
'inactiveCount',
'totalCount',
'search',
'status',
'region',
'regions',
));
}
public function create(): View
{
return view('countries::create', ['country' => new Country]);
}
public function store(StoreCountryRequest $request): RedirectResponse
{
$country = Country::query()->create($this->payload($request));
return redirect()
->route('countries.show', $country)
->with('success', 'Country created.');
}
public function show(Country $country, CountryUsageReport $usageReport): View
{
$country->load(['subdivisions' => fn ($query) => $query->ordered()->limit(250)]);
$usageRows = $usageReport->forCountry($country);
return view('countries::show', compact('country', 'usageRows'));
}
public function edit(Country $country): View
{
return view('countries::edit', compact('country'));
}
public function update(UpdateCountryRequest $request, Country $country): RedirectResponse
{
$country->update($this->payload($request));
return redirect()
->route('countries.show', $country)
->with('success', 'Country updated.');
}
public function destroy(Country $country): RedirectResponse
{
$country->delete();
return redirect()
->route('countries.index')
->with('success', 'Country archived.');
}
private function payload(StoreCountryRequest|UpdateCountryRequest $request): array
{
$data = $request->validated();
foreach (['alpha2', 'alpha3', 'currency_code', 'ioc_code', 'fifa_code'] as $key) {
if (isset($data[$key]) && is_string($data[$key])) {
$data[$key] = strtoupper(trim($data[$key]));
}
}
if (isset($data['numeric']) && is_string($data['numeric'])) {
$data['numeric'] = str_pad(trim($data['numeric']), 3, '0', STR_PAD_LEFT);
}
foreach (['languages', 'timezones', 'metadata'] as $jsonField) {
$value = $data[$jsonField] ?? null;
$data[$jsonField] = blank($value)
? null
: json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
}
foreach (['is_active', 'independent', 'un_member'] as $booleanField) {
$data[$booleanField] = $request->boolean($booleanField);
}
return $data;
}
}
@@ -0,0 +1,22 @@
<?php
namespace Modules\Countries\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Artisan;
class CountrySyncController extends Controller
{
/**
* Synchronise bundled ISO 3166 reference data.
*/
public function __invoke(): RedirectResponse
{
Artisan::call('countries:sync-iso', [
'--subdivisions' => true,
]);
return redirect()->route('countries.index')->with('success', 'ISO 3166 country reference data synchronised.');
}
}
@@ -0,0 +1,29 @@
<?php
namespace Modules\Countries\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
class ReferenceDataController extends Controller
{
public function currencies(): View
{
return view('countries::reference.currencies');
}
public function languages(): View
{
return view('countries::reference.languages');
}
public function scripts(): View
{
return view('countries::reference.scripts');
}
public function locodes(): View
{
return view('countries::reference.locodes');
}
}