Select a file from the repository tree to inspect its code.
app/Http/Controllers/ApiController.php
Copy Code
<?php
namespace App\Http\Controllers;
use App\Models\Country;
use App\Models\Currency;
use App\Models\State;
use App\Models\Tokens;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ApiController extends Controller
{
/**
* Authorize / Validate Token
*
* Check if a developer API key is valid and active.
* Note: This endpoint requires the token to be provided in the Bearer Token request header.
*/
public function authorize(Request $request): JsonResponse
{
$bearerToken = $request->bearerToken();
if (! $bearerToken) {
return response()->json([
'status' => false,
'message' => 'Token not provided.',
], 401);
}
$token = Tokens::where('token', $bearerToken)->where('active', true)->first();
if (! $token) {
return response()->json([
'status' => false,
'message' => 'Token is invalid or expired.',
], 401);
}
return response()->json([
'status' => true,
'message' => 'Token is valid.',
'name' => $token->name,
'default_currency' => $token->default_currency ?: 'INR',
]);
}
/**
* Get Rates by Default Currency
*
* Retrieve all active currency rates relative to the API token's default currency.
* If the token does not specify a default currency, the system fallback (INR) is used.
*/
public function getCurrencies(Request $request): JsonResponse
{
$token = $request->attributes->get('api_token');
$base = $token ? ($token->default_currency ?: 'INR') : 'INR';
$currencies = Cache::remember('currencies:all_list', now()->addDays(7), function () {
return Currency::where('active', true)->get()->keyBy('code')->toArray();
});
if (empty($currencies)) {
Artisan::call('app:sync-currencies');
Cache::forget('currencies:all_list');
$currencies = Cache::remember('currencies:all_list', now()->addDays(7), function () {
return Currency::where('active', true)->get()->keyBy('code')->toArray();
});
}
if (empty($currencies)) {
return response()->json([
'status' => false,
'message' => 'No active currency rates found in system.',
], 404);
}
$base = strtoupper($base);
if (! isset($currencies[$base])) {
$base = 'INR'; // fallback to INR
}
if (! isset($currencies[$base])) {
$base = array_key_first($currencies);
}
$baseRate = $currencies[$base]['rate'];
$convertedRates = [];
foreach ($currencies as $code => $curr) {
$convertedRates[$code] = [
'code' => $code,
'name' => $curr['name'],
'rate' => round($curr['rate'] / $baseRate, 8),
'country' => $curr['country'],
'flag' => $curr['flag'] ?? null,
'flag_png' => $curr['flag_png'] ?? null,
'flag_svg' => $curr['flag_svg'] ?? null,
'symbol' => $curr['symbol'] ?? null,
];
}
return response()->json([
'status' => true,
'disclaimer' => 'Usage subject to terms: '.url('/terms'),
'license' => 'Usage subject to license: '.url('/license'),
'timestamp' => now()->timestamp,
'base' => $base,
'rates' => $convertedRates,
], 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Get Rates by Custom Base Currency
*
* Retrieve all active currency rates relative to a specific base currency code passed in the request path.
*/
public function getAllByDefaultCurrencyPassedInRequest(Request $request, string $code): JsonResponse
{
$base = strtoupper($code);
$currencies = Cache::remember('currencies:all_list', now()->addDays(7), function () {
return Currency::where('active', true)->get()->keyBy('code')->toArray();
});
if (empty($currencies)) {
Artisan::call('app:sync-currencies');
Cache::forget('currencies:all_list');
$currencies = Cache::remember('currencies:all_list', now()->addDays(7), function () {
return Currency::where('active', true)->get()->keyBy('code')->toArray();
});
}
if (empty($currencies)) {
return response()->json([
'status' => false,
'message' => 'No active currency rates found in system.',
], 404);
}
if (! isset($currencies[$base])) {
return response()->json([
'status' => false,
'message' => "Currency code '{$base}' is not supported or active.",
], 400);
}
$baseRate = $currencies[$base]['rate'];
$convertedRates = [];
foreach ($currencies as $currCode => $curr) {
$convertedRates[$currCode] = [
'code' => $currCode,
'name' => $curr['name'],
'rate' => round($curr['rate'] / $baseRate, 8),
'country' => $curr['country'],
'flag' => $curr['flag'] ?? null,
'flag_png' => $curr['flag_png'] ?? null,
'flag_svg' => $curr['flag_svg'] ?? null,
'symbol' => $curr['symbol'] ?? null,
];
}
return response()->json([
'status' => true,
'disclaimer' => 'Usage subject to terms: '.url('/terms'),
'license' => 'Usage subject to license: '.url('/license'),
'timestamp' => now()->timestamp,
'base' => $base,
'rates' => $convertedRates,
], 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Convert Currency
*
* Calculate and convert a specified amount from a source currency to a target currency.
*/
public function convertCurrency(Request $request, string $from, string $to): JsonResponse
{
$from = strtoupper($from);
$to = strtoupper($to);
$amount = (float) $request->input('amount', 1.0);
if ($amount <= 0) {
return response()->json([
'status' => false,
'message' => 'Amount must be greater than zero.',
], 400);
}
$rates = Cache::remember('currencies:rates', now()->addDays(7), function () {
return Currency::where('active', true)->pluck('rate', 'code')->toArray();
});
if (empty($rates)) {
return response()->json([
'status' => false,
'message' => 'No active currency rates found in system.',
], 404);
}
if (! isset($rates[$from])) {
return response()->json([
'status' => false,
'message' => "Source currency code '{$from}' is not supported or active.",
], 400);
}
if (! isset($rates[$to])) {
return response()->json([
'status' => false,
'message' => "Target currency code '{$to}' is not supported or active.",
], 400);
}
$fromRate = $rates[$from];
$toRate = $rates[$to];
$converted = ($amount / $fromRate) * $toRate;
$rate = $toRate / $fromRate;
return response()->json([
'status' => true,
'from' => $from,
'to' => $to,
'amount' => $amount,
'converted' => round($converted, 4),
'rate' => round($rate, 6),
'timestamp' => now()->timestamp,
]);
}
/**
* Get Merged Geo and Currency dataset.
*/
public function allMerged(): JsonResponse
{
$merged = Cache::get('geo:all_merged');
if (empty($merged)) {
$merged = $this->compileMergedGeo();
}
if (empty($merged)) {
return response()->json([
'status' => false,
'message' => 'Geo and currency data not synced or cached. Please run sync command.',
], 404);
}
return response()->json($merged, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Get all active countries.
*/
public function countries(): JsonResponse
{
if (DB::table('countries')->count() === 0 || DB::table('currencies')->count() === 0) {
Artisan::call('app:sync-currencies');
Cache::forget('geo:countries');
}
$countries = Cache::remember('geo:countries', now()->addDays(7), function () {
return DB::table('countries')->get(['id', 'name', 'sortname', 'currency_code'])->map(function ($item) {
return (array) $item;
})->toArray();
});
if (empty($countries)) {
return response()->json([
'status' => false,
'message' => 'Geo countries data not found.',
], 404);
}
$currencies = Currency::where('active', true)->get()->keyBy('code');
$responseCountries = [];
foreach ($countries as $c) {
$currencyCode = $c['currency_code'] ?? null;
$currModel = $currencyCode ? ($currencies[$currencyCode] ?? null) : null;
$responseCountries[] = [
'name' => $c['name'],
'iso_code' => $c['sortname'] ?? null,
'currency' => $currModel ? [
'name' => $currModel->name,
'code' => $currModel->code,
'symbol' => $currModel->symbol,
'flag' => $currModel->flag,
'flag_png' => $currModel->flag_png,
'flag_svg' => $currModel->flag_svg,
'rate' => (float) $currModel->rate,
'country' => $currModel->country,
'active' => (bool) $currModel->active,
] : null,
];
}
return response()->json([
'status' => true,
'countries' => $responseCountries,
], 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Get states of a country.
*/
public function states(string $code): JsonResponse
{
$code = strtoupper($code);
if (DB::table('countries')->count() === 0 || DB::table('currencies')->count() === 0) {
Artisan::call('app:sync-currencies');
Cache::forget('geo:countries');
Cache::forget('geo:states_list');
}
$countries = Cache::remember('geo:countries', now()->addDays(7), function () {
return DB::table('countries')->get(['id', 'name', 'sortname', 'currency_code'])->map(function ($item) {
return (array) $item;
})->toArray();
});
$statesList = Cache::remember('geo:states_list', now()->addDays(7), function () {
return DB::table('states')->get(['id', 'name', 'country_id'])->map(function ($item) {
return (array) $item;
})->toArray();
});
$countryId = null;
$targetCountry = null;
foreach ($countries as $c) {
$cca2 = $c['sortname'] ?? null;
if ($cca2 && (strtoupper($cca2) === $code || strtolower($c['name']) === strtolower($code))) {
$countryId = $c['id'];
$targetCountry = $c;
break;
}
}
if ($countryId === null) {
return response()->json([
'status' => false,
'message' => "Country with code/name '{$code}' not found.",
], 404);
}
$currencies = Currency::where('active', true)->get()->keyBy('code');
$currencyCode = $targetCountry['currency_code'] ?? null;
$currModel = $currencyCode ? ($currencies[$currencyCode] ?? null) : null;
$countryResponse = [
'name' => $targetCountry['name'],
'iso_code' => $targetCountry['sortname'] ?? null,
'currency' => $currModel ? [
'name' => $currModel->name,
'code' => $currModel->code,
'symbol' => $currModel->symbol,
'flag' => $currModel->flag,
'flag_png' => $currModel->flag_png,
'flag_svg' => $currModel->flag_svg,
'rate' => (float) $currModel->rate,
'country' => $currModel->country,
'active' => (bool) $currModel->active,
] : null,
];
$states = [];
foreach ($statesList as $s) {
if ($s['country_id'] == $countryId) {
$states[] = [
'name' => $s['name'],
'country' => $countryResponse,
];
}
}
return response()->json([
'status' => true,
'country_code' => $code,
'states' => $states,
], 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Get cities of a state.
*/
public function cities(string $name): JsonResponse
{
if (DB::table('countries')->count() === 0 || DB::table('currencies')->count() === 0) {
Artisan::call('app:sync-currencies');
Cache::forget('geo:countries');
Cache::forget('geo:states_list');
Cache::forget('geo:cities_list');
}
$statesList = Cache::remember('geo:states_list', now()->addDays(7), function () {
return DB::table('states')->get(['id', 'name', 'country_id'])->map(function ($item) {
return (array) $item;
})->toArray();
});
$stateId = null;
$targetState = null;
foreach ($statesList as $s) {
if (strtolower($s['name']) === strtolower($name)) {
$stateId = $s['id'];
$targetState = $s;
break;
}
}
if ($stateId === null) {
return response()->json([
'status' => false,
'message' => "State '{$name}' not found.",
], 404);
}
// Find country
$countryId = $targetState['country_id'];
$countries = Cache::remember('geo:countries', now()->addDays(7), function () {
return DB::table('countries')->get(['id', 'name', 'sortname', 'currency_code'])->map(function ($item) {
return (array) $item;
})->toArray();
});
$targetCountry = null;
foreach ($countries as $c) {
if ($c['id'] == $countryId) {
$targetCountry = $c;
break;
}
}
$countryResponse = null;
if ($targetCountry) {
$currencies = Currency::where('active', true)->get()->keyBy('code');
$currencyCode = $targetCountry['currency_code'] ?? null;
$currModel = $currencyCode ? ($currencies[$currencyCode] ?? null) : null;
$countryResponse = [
'name' => $targetCountry['name'],
'iso_code' => $targetCountry['sortname'] ?? null,
'currency' => $currModel ? [
'name' => $currModel->name,
'code' => $currModel->code,
'symbol' => $currModel->symbol,
'flag' => $currModel->flag,
'flag_png' => $currModel->flag_png,
'flag_svg' => $currModel->flag_svg,
'rate' => (float) $currModel->rate,
'country' => $currModel->country,
'active' => (bool) $currModel->active,
] : null,
];
}
$stateResponse = [
'name' => $targetState['name'],
'country' => $countryResponse,
];
$cities = [];
if (Cache::has('geo:cities_list')) {
$citiesList = Cache::get('geo:cities_list');
foreach ($citiesList as $c) {
if ($c['state_id'] == $stateId) {
$cities[] = [
'name' => $c['name'],
'state' => $stateResponse,
];
}
}
} else {
$cities = Cache::remember("geo:state_cities_{$stateId}", now()->addDays(7), function () use ($stateId, $stateResponse) {
return DB::table('cities')
->where('state_id', $stateId)
->get(['name'])
->map(function ($c) use ($stateResponse) {
return [
'name' => $c->name,
'state' => $stateResponse,
];
})
->toArray();
});
}
return response()->json([
'status' => true,
'state_name' => $name,
'cities' => $cities,
], 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Compile Geo cache on the fly from the database.
*/
protected function compileMergedGeo(): array
{
if (DB::table('countries')->count() === 0 || DB::table('currencies')->count() === 0) {
Artisan::call('app:sync-currencies');
$merged = Cache::get('geo:all_merged');
if (! empty($merged)) {
return $merged;
}
}
$dbCountries = DB::table('countries')->get(['id', 'name', 'sortname', 'currency_code'])->toArray();
$dbStates = DB::table('states')->get(['id', 'name', 'country_id'])->toArray();
$dbCities = DB::table('cities')->get(['id', 'name', 'state_id'])->toArray();
$countryStates = [];
foreach ($dbStates as $s) {
$countryStates[$s->country_id][] = [
'id' => $s->id,
'name' => $s->name,
];
}
$stateCities = [];
foreach ($dbCities as $c) {
$stateCities[$c->state_id][] = $c->name;
}
$mergedGeo = [];
$activeCurrenciesMapped = Currency::where('active', true)->get()->keyBy('code')->toArray();
foreach ($dbCountries as $c) {
$currCode = $c->currency_code ?? null;
if ($currCode && isset($activeCurrenciesMapped[$currCode])) {
$curr = $activeCurrenciesMapped[$currCode];
$countryKey = strtolower($c->name);
$states = [];
$sList = $countryStates[$c->id] ?? [];
foreach ($sList as $stateInfo) {
$cities = $stateCities[$stateInfo['id']] ?? [];
$states[$stateInfo['name']] = $cities;
}
$mergedGeo[$countryKey] = [
'currency' => [
'name' => $curr['name'],
'code' => $curr['code'],
'symbol' => $curr['symbol'] ?? null,
'flag' => $curr['flag'],
'flag_png' => $curr['flag_png'],
'flag_svg' => $curr['flag_svg'],
'rate' => (float) $curr['rate'],
'country' => $curr['country'],
'active' => (bool) $curr['active'],
],
'states' => $states,
];
}
}
$countriesCache = array_map(function ($item) {
return (array) $item;
}, $dbCountries);
$statesCache = array_map(function ($item) {
return (array) $item;
}, $dbStates);
$citiesCache = array_map(function ($item) {
return (array) $item;
}, $dbCities);
Cache::put('geo:countries', $countriesCache, now()->addDays(7));
Cache::put('geo:states_list', $statesCache, now()->addDays(7));
Cache::put('geo:cities_list', $citiesCache, now()->addDays(7));
Cache::put('geo:all_merged', $mergedGeo, now()->addDays(7));
return $mergedGeo;
}
}