Add monthly views chart
This commit is contained in:
@@ -60,13 +60,6 @@ class CacheHelper
|
||||
});
|
||||
}
|
||||
|
||||
public static function getTotalMonthlyViews()
|
||||
{
|
||||
return Cache::remember("total_monthly_view_count", now()->addMinutes(60), function () {
|
||||
return PopularMonthly::count();
|
||||
});
|
||||
}
|
||||
|
||||
public static function getPopularAllTime(bool $guest)
|
||||
{
|
||||
$guestString = $guest ? 'guest' : 'authed';
|
||||
|
@@ -2,16 +2,18 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Downloads;
|
||||
use App\Models\Hentai;
|
||||
use App\Models\PopularMonthly;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class HentaiApiController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
/**
|
||||
* Get a list of all hentai with it's episodes
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Cache for 10 minutes
|
||||
$data = Cache::remember('api_hentai_list', now()->addMinutes(10), function () {
|
||||
@@ -35,4 +37,20 @@ class HentaiApiController extends Controller
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monthly views by day for stats
|
||||
*/
|
||||
public function getMonthlyViews()
|
||||
{
|
||||
// Cache for 60 minutes
|
||||
$data = Cache::remember('api_hentai_list', now()->addMinutes(60), function () {
|
||||
return PopularMonthly::selectRaw('DATE(created_at) as date, COUNT(*) as count')
|
||||
->groupBy('date')
|
||||
->orderBy('date', 'asc')
|
||||
->get();
|
||||
});
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
|
@@ -87,7 +87,6 @@ class HomeController extends Controller
|
||||
'viewCount' => CacheHelper::getTotalViewCount(),
|
||||
'episodeCount' => CacheHelper::getTotalEpisodeCount(),
|
||||
'hentaiCount' => CacheHelper::getTotalHentaiCount(),
|
||||
'monthlyCount' => CacheHelper::getTotalMonthlyViews()
|
||||
]);
|
||||
}
|
||||
|
||||
|
50
package-lock.json
generated
50
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@jellyfin/libass-wasm": "^4.1.1",
|
||||
"@yaireo/tagify": "^4.21.2",
|
||||
"chart.js": "^4.5.0",
|
||||
"dashjs": "^5.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"plyr": "^3.7.8",
|
||||
@@ -572,6 +573,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -1267,10 +1274,16 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
|
||||
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
|
||||
"license": "MIT"
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-plugin-datalabels": {
|
||||
"version": "2.2.0",
|
||||
@@ -3142,6 +3155,12 @@
|
||||
"tailwindcss": "3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tw-elements/node_modules/chart.js": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
|
||||
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tw-elements/node_modules/lilconfig": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||
@@ -3258,6 +3277,15 @@
|
||||
"postcss": "^8.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/tw-elements/node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "1.0.41",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
|
||||
@@ -3615,12 +3643,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": ">= 14.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@jellyfin/libass-wasm": "^4.1.1",
|
||||
"@yaireo/tagify": "^4.21.2",
|
||||
"chart.js": "^4.5.0",
|
||||
"dashjs": "^5.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"plyr": "^3.7.8",
|
||||
|
73
resources/js/stats.js
Normal file
73
resources/js/stats.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
// Theming
|
||||
if (localStorage.theme !== 'light') {
|
||||
Chart.defaults.color = "#ADBABD";
|
||||
Chart.defaults.borderColor = "rgba(255,255,255,0.1)";
|
||||
Chart.defaults.backgroundColor = "rgba(255,255,0,0.1)";
|
||||
Chart.defaults.elements.line.borderColor = "rgba(255,255,0,0.4)";
|
||||
}
|
||||
|
||||
// Get Tags from API
|
||||
window.axios.get('/v1/monthly-views').then(function (response) {
|
||||
if (response.status != 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
labels: response.data.map((entry) => { return entry.date }),
|
||||
datasets: [{
|
||||
label: 'Views',
|
||||
fill: false,
|
||||
backgroundColor: 'rgba(190, 18, 60, 0.3)',
|
||||
borderColor: 'rgba(190, 18, 60, 1.0)',
|
||||
cubicInterpolationMode: 'monotone',
|
||||
data: response.data.map((entry) => { return entry.count }),
|
||||
}]
|
||||
}
|
||||
|
||||
const config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Views the last 30 days',
|
||||
font: {
|
||||
size: 18
|
||||
}
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Views'
|
||||
},
|
||||
suggestedMin: 0,
|
||||
suggestedMax: 40000
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const monthlyViewChart = new Chart(
|
||||
document.getElementById('monthlyChart'),
|
||||
config
|
||||
);
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="flex justify-center pb-10">
|
||||
<img src="/images/cropped-HS-1-270x270.webp" class="max-w-[150px]" alt="hstream.moe Logo" />
|
||||
</div>
|
||||
<div class="grid md:grid-cols-5 lg:gap-x-12">
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 lg:gap-x-12">
|
||||
<div class="mb-12 md:mb-0">
|
||||
<div class="mb-6 inline-block rounded-md bg-white dark:bg-neutral-950 p-4 text-sky-500">
|
||||
<i class="fa-solid fa-eye text-3xl"> {{ number_format($viewCount) }}</i>
|
||||
@@ -14,15 +14,7 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="mb-12 md:mb-0">
|
||||
<div class="mb-6 inline-block rounded-md bg-white dark:bg-neutral-950 p-4 text-sky-500">
|
||||
<i class="fa-solid fa-calendar-days text-3xl"> {{ number_format($monthlyCount) }}</i>
|
||||
</div>
|
||||
<h5 class="text-lg font-medium dark:text-neutral-300">
|
||||
views the last 30 days
|
||||
</h5>
|
||||
</div>
|
||||
<div class="mb-12 md:mb-0">
|
||||
<div class="mb-6 inline-block rounded-md bg-white dark:bg-neutral-950 p-4 text-rose-600">
|
||||
<div class="b-6 inline-block rounded-md bg-white dark:bg-neutral-950 p-4 text-sky-500">
|
||||
<i class="fa-solid fa-video text-3xl"> {{ $episodeCount }}</i>
|
||||
</div>
|
||||
<h5 class="text-lg font-medium dark:text-neutral-300">
|
||||
@@ -46,7 +38,10 @@
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center dark:bg-neutral-950 bg-gray-50 rounded-xl md:m-11 hidden md:block">
|
||||
<canvas id="monthlyChart"></canvas>
|
||||
</div>
|
||||
</section>
|
||||
<p class="text-center text-black/40 dark:text-white/40">Cached for 60 Minutes</p>
|
||||
</div>
|
||||
@vite(['resources/js/stats.js'])
|
||||
</x-app-layout>
|
||||
|
@@ -26,6 +26,7 @@ Route::get('/banned', [HomeController::class, 'banned'])->name('home.banned');
|
||||
|
||||
// API Endpoint
|
||||
Route::get('/v1/hentai-list', [HentaiApiController::class, 'index'])->name('api.hentai.index');
|
||||
Route::get('/v1/monthly-views', [HentaiApiController::class, 'getMonthlyViews'])->name('api.hentai.monthly');
|
||||
|
||||
// Stream Page
|
||||
Route::get('/hentai/{title}', [StreamController::class, 'index'])->name('hentai.index');
|
||||
|
Reference in New Issue
Block a user