perf: optimize database queries and caching

This commit is contained in:
Rico van Zelst
2026-01-01 00:41:38 +01:00
parent 329e071e2b
commit dd79ffcf98
9 changed files with 147 additions and 101 deletions

View File

@@ -15,7 +15,7 @@ function getRoleIcon($roleName): string
'Support' => 'gm-support.png', 'Support' => 'gm-support.png',
]; ];
return asset('img/'.$roleIcons[$roleName]); return asset('img/' . $roleIcons[$roleName]);
} }
function getAverageColorFromImageUrl($imageUrl): string function getAverageColorFromImageUrl($imageUrl): string
@@ -75,9 +75,13 @@ function getRoleIconSvg($roleName): string
*/ */
function getChampionImage($full_id, $type): string function getChampionImage($full_id, $type): string
{ {
$championImage = ChampionImage::where('full_id', $full_id)->where('type', $type)->first(); $cacheKey = "champion_image_{$full_id}_{$type}";
if (! $championImage) { $championImage = Cache::remember($cacheKey, 60 * 60 * 24, static function () use ($full_id, $type) {
return ChampionImage::where('full_id', $full_id)->where('type', $type)->first();
});
if (!$championImage) {
return ''; return '';
} }
@@ -92,7 +96,7 @@ function getCommitHash(): string
/** /**
* @var string $commit * @var string $commit
*/ */
$commit = Cache::remember('commit_hash', 60 * 72, fn () => trim(exec('git log --pretty="%h" -n1 HEAD'))); $commit = Cache::remember('commit_hash', 60 * 72, fn() => trim(exec('git log --pretty="%h" -n1 HEAD')));
return $commit; return $commit;
} }

View File

@@ -31,7 +31,7 @@ class ChampionController extends Controller
$champion = Cache::remember('championShowCache' . $champion->slug, $threeDaysInSeconds, static fn() => $champion->load('streamers', 'skins', 'lanes')); $champion = Cache::remember('championShowCache' . $champion->slug, $threeDaysInSeconds, static fn() => $champion->load('streamers', 'skins', 'lanes'));
$streamers = $champion->load('streamers')->streamers; $streamers = $champion->streamers;
return view('champions.show', ['champion' => $champion, 'streamers' => $streamers]); return view('champions.show', ['champion' => $champion, 'streamers' => $streamers]);
} }

View File

@@ -14,13 +14,13 @@ class HomeController extends Controller
$query->where('release_date', '0000-00-00') $query->where('release_date', '0000-00-00')
->orWhere('release_date', '>', now()); ->orWhere('release_date', '>', now());
}) })
->orderBy('release_date', 'desc')->get()); ->orderBy('release_date', 'desc')->limit(6)->get());
$latestSkins = Cache::remember('latestSkins_home', 60 * 4, static fn() => ChampionSkin::where('release_date', '!=', '0000-00-00') $latestSkins = Cache::remember('latestSkins_home', 60 * 4, static fn() => ChampionSkin::where('release_date', '!=', '0000-00-00')
->where('availability', '!=', 'Upcoming') ->where('availability', '!=', 'Upcoming')
->where('release_date', '<=', now()) ->where('release_date', '<=', now())
->orderBy('release_date', 'desc')->get()); ->orderBy('release_date', 'desc')->limit(6)->get());
return view('home', [ return view('home', [
'latestSkins' => $latestSkins, 'latestSkins' => $latestSkins,

View File

@@ -4,17 +4,22 @@ namespace App\Http\Controllers;
use App\Models\SummonerEmote; use App\Models\SummonerEmote;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\QueryBuilder;
class SummonerEmoteController extends Controller class SummonerEmoteController extends Controller
{ {
public function index() public function index()
{ {
$emotes = QueryBuilder::for(SummonerEmote::class) $cacheKey = 'emotes_' . md5(serialize(request()->query()));
->allowedFilters('title')
->defaultSort('-emote_id') $emotes = Cache::remember($cacheKey, 60 * 60, function () {
->paginate(72) return QueryBuilder::for(SummonerEmote::class)
->appends(request()->query()); ->allowedFilters('title')
->defaultSort('-emote_id')
->paginate(72)
->appends(request()->query());
});
return view('emotes.index', ['emotes' => $emotes]); return view('emotes.index', ['emotes' => $emotes]);
} }

View File

@@ -4,17 +4,22 @@ namespace App\Http\Controllers;
use App\Models\SummonerIcon; use App\Models\SummonerIcon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\QueryBuilder;
class SummonerIconController extends Controller class SummonerIconController extends Controller
{ {
public function index() public function index()
{ {
$icons = QueryBuilder::for(SummonerIcon::class) $cacheKey = 'icons_' . md5(serialize(request()->query()));
->allowedFilters(['title', 'esports_team', 'release_year'])
->defaultSort('-icon_id') $icons = Cache::remember($cacheKey, 60 * 60, function () {
->paginate(72) return QueryBuilder::for(SummonerIcon::class)
->appends(request()->query()); ->allowedFilters(['title', 'esports_team', 'release_year'])
->defaultSort('-icon_id')
->paginate(72)
->appends(request()->query());
});
return view('icons.index', ['icons' => $icons]); return view('icons.index', ['icons' => $icons]);
} }

View File

@@ -22,7 +22,7 @@ class ChampionSkin extends Model
'availability', 'availability',
'loot_eligible', 'loot_eligible',
'rp_price', 'rp_price',
'raritiy', 'rarity',
'release_date', 'release_date',
'associated_skinline', 'associated_skinline',
'new_effects', 'new_effects',
@@ -70,12 +70,12 @@ class ChampionSkin extends Model
public function getSkinImageLoadingAttribute(): string public function getSkinImageLoadingAttribute(): string
{ {
return 'https://cdn.communitydragon.org/latest/champion/'.$this->champion_id.'/portrait/skin/'.$this->skin_id; return 'https://cdn.communitydragon.org/latest/champion/' . $this->champion_id . '/portrait/skin/' . $this->skin_id;
} }
public function getSkinImageTileAttribute(): string public function getSkinImageTileAttribute(): string
{ {
return 'https://cdn.communitydragon.org/latest/champion/'.$this->champion_id.'/tile/skin/'.$this->skin_id; return 'https://cdn.communitydragon.org/latest/champion/' . $this->champion_id . '/tile/skin/' . $this->skin_id;
} }
protected function casts(): array protected function casts(): array

View File

@@ -0,0 +1,74 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
private function indexExists(string $table, string $indexName): bool
{
$result = DB::select("SHOW INDEX FROM {$table} WHERE Key_name = ?", [$indexName]);
return count($result) > 0;
}
public function up(): void
{
Schema::table('champion_skins', function (Blueprint $table) {
if (!$this->indexExists('champion_skins', 'champion_skins_availability_index')) {
$table->index('availability');
}
if (!$this->indexExists('champion_skins', 'champion_skins_release_date_index')) {
$table->index('release_date');
}
if (!$this->indexExists('champion_skins', 'champion_skins_slug_index')) {
$table->index('slug');
}
if (!$this->indexExists('champion_skins', 'champion_skins_rarity_index')) {
$table->index('rarity');
}
});
Schema::table('summoner_icons', function (Blueprint $table) {
if (!$this->indexExists('summoner_icons', 'summoner_icons_title_index')) {
$table->index('title');
}
if (!$this->indexExists('summoner_icons', 'summoner_icons_esports_team_index')) {
$table->index('esports_team');
}
if (!$this->indexExists('summoner_icons', 'summoner_icons_release_year_index')) {
$table->index('release_year');
}
});
}
public function down(): void
{
Schema::table('champion_skins', function (Blueprint $table) {
if ($this->indexExists('champion_skins', 'champion_skins_availability_index')) {
$table->dropIndex(['availability']);
}
if ($this->indexExists('champion_skins', 'champion_skins_release_date_index')) {
$table->dropIndex(['release_date']);
}
if ($this->indexExists('champion_skins', 'champion_skins_slug_index')) {
$table->dropIndex(['slug']);
}
if ($this->indexExists('champion_skins', 'champion_skins_rarity_index')) {
$table->dropIndex(['rarity']);
}
});
Schema::table('summoner_icons', function (Blueprint $table) {
if ($this->indexExists('summoner_icons', 'summoner_icons_title_index')) {
$table->dropIndex(['title']);
}
if ($this->indexExists('summoner_icons', 'summoner_icons_esports_team_index')) {
$table->dropIndex(['esports_team']);
}
if ($this->indexExists('summoner_icons', 'summoner_icons_release_year_index')) {
$table->dropIndex(['release_year']);
}
});
}
};

View File

@@ -6,30 +6,6 @@
navigation: auto; /* enabled! */ navigation: auto; /* enabled! */
} }
@font-face {
font-display: swap;
font-family: "Inter";
font-style: normal;
font-weight: 100;
src: url("/fonts/inter-v13-latin-100.woff2") format("woff2");
}
@font-face {
font-display: swap;
font-family: "Inter";
font-style: normal;
font-weight: 200;
src: url("/fonts/inter-v13-latin-200.woff2") format("woff2");
}
@font-face {
font-display: swap;
font-family: "Inter";
font-style: normal;
font-weight: 300;
src: url("/fonts/inter-v13-latin-300.woff2") format("woff2");
}
@font-face { @font-face {
font-display: swap; font-display: swap;
font-family: "Inter"; font-family: "Inter";
@@ -62,22 +38,6 @@
src: url("/fonts/inter-v13-latin-700.woff2") format("woff2"); src: url("/fonts/inter-v13-latin-700.woff2") format("woff2");
} }
@font-face {
font-display: swap;
font-family: "Inter";
font-style: normal;
font-weight: 800;
src: url("/fonts/inter-v13-latin-800.woff2") format("woff2");
}
@font-face {
font-display: swap;
font-family: "Inter";
font-style: normal;
font-weight: 900;
src: url("/fonts/inter-v13-latin-900.woff2") format("woff2");
}
.glow-shadow::before { .glow-shadow::before {
content: ""; content: "";
position: absolute; position: absolute;

View File

@@ -17,57 +17,55 @@
</div> </div>
<div class="grid grid-cols-1 gap-4 mt-8 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3"> <div class="grid grid-cols-1 gap-4 mt-8 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3">
@foreach ($latestSkins as $skin) @foreach ($latestSkins as $skin)
@if ($loop->index < 6) <div
<div class="p-8 transition border shadow-xl bg-stone-800/40 border-stone-800 rounded-xl hover:border-orange-500/10 hover:shadow-orange-500/10">
class="p-8 transition border shadow-xl bg-stone-800/40 border-stone-800 rounded-xl hover:border-orange-500/10 hover:shadow-orange-500/10"> <div class="flex flex-col">
<div class="flex flex-col">
<div class="flex flex-col items-center justify-center">
<a href="{{ route('skins.show', $skin->slug) }}">
<img loading="lazy" class="h-full border-2 w-80 border-orange-400/40 rounded-xl"
src="//wsrv.nl/?url={{ $skin->getSkinImageAttribute() }}&w=480&output=webp&q=75&maxage=7d&default=i.ibb.co/xt3D5LMt/heimerdingerlol-placeholder.webp"
alt="{{ $skin->skin_name }} Splash Art">
</a>
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<h2 class="mt-4 text-xl font-bold text-white"><a <a href="{{ route('skins.show', $skin->slug) }}">
href="{{ route('skins.show', $skin->slug) }}">{{ $skin->skin_name }}</a> <img loading="lazy" class="h-full border-2 w-80 border-orange-400/40 rounded-xl"
</h2> src="//wsrv.nl/?url={{ $skin->getSkinImageAttribute() }}&w=480&output=webp&q=75&maxage=7d&default=i.ibb.co/xt3D5LMt/heimerdingerlol-placeholder.webp"
<h3 class="text-stone-200">Released alt="{{ $skin->skin_name }} Splash Art">
{{ Carbon::parse($skin->release_date)->diffForHumans([ </a>
'parts' => 2, <div class="flex flex-col items-center justify-center">
'join' => true, <h2 class="mt-4 text-xl font-bold text-white"><a
]) }} href="{{ route('skins.show', $skin->slug) }}">{{ $skin->skin_name }}</a>
</h3> </h2>
<h3 class="text-stone-200">Released
{{ Carbon::parse($skin->release_date)->diffForHumans([
'parts' => 2,
'join' => true,
]) }}
</h3>
@foreach ($skin->associated_skinline as $skinline) @foreach ($skin->associated_skinline as $skinline)
<span <span
class="my-2 bg-orange-100 text-orange-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded border border-orange-300"> class="my-2 bg-orange-100 text-orange-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded border border-orange-300">
{{ $skinline }}</span> {{ $skinline }}</span>
@endforeach @endforeach
<p class="flex text-sm text-stone-300"> <p class="flex text-sm text-stone-300">
@if ($skin->rp_price == '99999') @if ($skin->rp_price == '99999')
Not Available for RP Not Available for RP
@else @else
<x-icon-RiotPoints class="w-4 mr-1 text-yellow-400" /> <x-icon-RiotPoints class="w-4 mr-1 text-yellow-400" />
{{ $skin->rp_price }} RP {{ $skin->rp_price }} RP
@endif @endif
</p>
@if ($skin->loot_eligible)
<p class="flex items-center mt-1 text-sm italic text-stone-300">
<x-icon-loot class="w-4 mr-1 text-yellow-200" />
Can be obtained from loot
</p> </p>
@endif
@if ($skin->loot_eligible)
<p class="flex items-center mt-1 text-sm italic text-stone-300">
<x-icon-loot class="w-4 mr-1 text-yellow-200" />
Can be obtained from loot
</p>
@endif
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
@endif
@endforeach @endforeach
</div> </div>
</div> </div>
</section> </section>