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)
|
public static function getPopularAllTime(bool $guest)
|
||||||
{
|
{
|
||||||
$guestString = $guest ? 'guest' : 'authed';
|
$guestString = $guest ? 'guest' : 'authed';
|
||||||
|
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Models\Downloads;
|
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
|
use App\Models\PopularMonthly;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
class HentaiApiController extends 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
|
// Cache for 10 minutes
|
||||||
$data = Cache::remember('api_hentai_list', now()->addMinutes(10), function () {
|
$data = Cache::remember('api_hentai_list', now()->addMinutes(10), function () {
|
||||||
@@ -35,4 +37,20 @@ class HentaiApiController extends Controller
|
|||||||
|
|
||||||
return response()->json($data);
|
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(),
|
'viewCount' => CacheHelper::getTotalViewCount(),
|
||||||
'episodeCount' => CacheHelper::getTotalEpisodeCount(),
|
'episodeCount' => CacheHelper::getTotalEpisodeCount(),
|
||||||
'hentaiCount' => CacheHelper::getTotalHentaiCount(),
|
'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",
|
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||||
"@jellyfin/libass-wasm": "^4.1.1",
|
"@jellyfin/libass-wasm": "^4.1.1",
|
||||||
"@yaireo/tagify": "^4.21.2",
|
"@yaireo/tagify": "^4.21.2",
|
||||||
|
"chart.js": "^4.5.0",
|
||||||
"dashjs": "^5.0.0",
|
"dashjs": "^5.0.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"plyr": "^3.7.8",
|
"plyr": "^3.7.8",
|
||||||
@@ -572,6 +573,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -1267,10 +1274,16 @@
|
|||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "3.9.1",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||||
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
|
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=8"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chartjs-plugin-datalabels": {
|
"node_modules/chartjs-plugin-datalabels": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
@@ -3142,6 +3155,12 @@
|
|||||||
"tailwindcss": "3.3.0"
|
"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": {
|
"node_modules/tw-elements/node_modules/lilconfig": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||||
@@ -3258,6 +3277,15 @@
|
|||||||
"postcss": "^8.0.9"
|
"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": {
|
"node_modules/ua-parser-js": {
|
||||||
"version": "1.0.41",
|
"version": "1.0.41",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
|
||||||
@@ -3615,12 +3643,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "1.10.2",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 14.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||||
"@jellyfin/libass-wasm": "^4.1.1",
|
"@jellyfin/libass-wasm": "^4.1.1",
|
||||||
"@yaireo/tagify": "^4.21.2",
|
"@yaireo/tagify": "^4.21.2",
|
||||||
|
"chart.js": "^4.5.0",
|
||||||
"dashjs": "^5.0.0",
|
"dashjs": "^5.0.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"plyr": "^3.7.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">
|
<div class="flex justify-center pb-10">
|
||||||
<img src="/images/cropped-HS-1-270x270.webp" class="max-w-[150px]" alt="hstream.moe Logo" />
|
<img src="/images/cropped-HS-1-270x270.webp" class="max-w-[150px]" alt="hstream.moe Logo" />
|
||||||
</div>
|
</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-12 md:mb-0">
|
||||||
<div class="mb-6 inline-block rounded-md bg-white dark:bg-neutral-950 p-4 text-sky-500">
|
<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>
|
<i class="fa-solid fa-eye text-3xl"> {{ number_format($viewCount) }}</i>
|
||||||
@@ -14,15 +14,7 @@
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-12 md:mb-0">
|
<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">
|
<div class="b-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">
|
|
||||||
<i class="fa-solid fa-video text-3xl"> {{ $episodeCount }}</i>
|
<i class="fa-solid fa-video text-3xl"> {{ $episodeCount }}</i>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="text-lg font-medium dark:text-neutral-300">
|
<h5 class="text-lg font-medium dark:text-neutral-300">
|
||||||
@@ -46,7 +38,10 @@
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="flex justify-center dark:bg-neutral-950 bg-gray-50 rounded-xl md:m-11 hidden md:block">
|
||||||
<p class="text-center text-black/40 dark:text-white/40">Cached for 60 Minutes</p>
|
<canvas id="monthlyChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
@vite(['resources/js/stats.js'])
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
@@ -26,6 +26,7 @@ Route::get('/banned', [HomeController::class, 'banned'])->name('home.banned');
|
|||||||
|
|
||||||
// API Endpoint
|
// API Endpoint
|
||||||
Route::get('/v1/hentai-list', [HentaiApiController::class, 'index'])->name('api.hentai.index');
|
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
|
// Stream Page
|
||||||
Route::get('/hentai/{title}', [StreamController::class, 'index'])->name('hentai.index');
|
Route::get('/hentai/{title}', [StreamController::class, 'index'])->name('hentai.index');
|
||||||
|
Reference in New Issue
Block a user