build view count total

This commit is contained in:
2026-03-28 22:12:53 -05:00
parent 98e5a5a740
commit b08e9523e4
4 changed files with 44 additions and 3 deletions

View File

@@ -36,6 +36,20 @@ export async function adminRoutes(app: FastifyInstance) {
return { total: pending.length, indexed: done, no_text_found: failed }; return { total: pending.length, indexed: done, no_text_found: failed };
}); });
/**
* GET /api/admin/stats
* Aggregate share and view counts across all memes.
*/
app.get('/api/admin/stats', { preHandler: requireAuth }, async () => {
const row = db
.prepare('SELECT SUM(share_count) as total_shares, SUM(view_count) as total_views FROM memes')
.get() as { total_shares: number | null; total_views: number | null };
return {
total_shares: row.total_shares ?? 0,
total_views: row.total_views ?? 0,
};
});
/** /**
* GET /api/admin/reindex/status * GET /api/admin/reindex/status
* Returns how many memes still need OCR indexing. * Returns how many memes still need OCR indexing.

View File

@@ -155,6 +155,10 @@ export const api = {
reindex(): Promise<{ total: number; indexed: number; no_text_found: number }> { reindex(): Promise<{ total: number; indexed: number; no_text_found: number }> {
return apiFetch('/api/admin/reindex', { method: 'POST' }); return apiFetch('/api/admin/reindex', { method: 'POST' });
}, },
stats(): Promise<{ total_shares: number; total_views: number }> {
return apiFetch('/api/admin/stats');
},
}, },
imageUrl(filePath: string): string { imageUrl(filePath: string): string {

View File

@@ -1,5 +1,6 @@
import { X, ScanText, Database, RefreshCw, CheckCircle2, AlertCircle, Loader2 } from 'lucide-react'; import React from 'react';
import { useReindexStatus, useReindex, useCollections, useTags, useMemes } from '../hooks/useMemes'; import { X, ScanText, Database, RefreshCw, CheckCircle2, AlertCircle, Loader2, Share2, MousePointerClick } from 'lucide-react';
import { useReindexStatus, useReindex, useCollections, useTags, useMemes, useAdminStats } from '../hooks/useMemes';
interface Props { interface Props {
onClose: () => void; onClose: () => void;
@@ -11,6 +12,7 @@ export function SettingsModal({ onClose }: Props) {
const { data: collections } = useCollections(); const { data: collections } = useCollections();
const { data: tags } = useTags(); const { data: tags } = useTags();
const { data: allMemes } = useMemes({ parent_only: false, limit: 1 }); const { data: allMemes } = useMemes({ parent_only: false, limit: 1 });
const { data: adminStats } = useAdminStats();
async function handleReindex() { async function handleReindex() {
await reindex.mutateAsync(); await reindex.mutateAsync();
@@ -45,6 +47,18 @@ export function SettingsModal({ onClose }: Props) {
<StatCard label="Collections" value={collections?.length ?? '—'} /> <StatCard label="Collections" value={collections?.length ?? '—'} />
<StatCard label="Tags" value={tags?.length ?? '—'} /> <StatCard label="Tags" value={tags?.length ?? '—'} />
</div> </div>
<div className="grid grid-cols-2 gap-2 mt-2">
<StatCard
label="Total shares"
value={adminStats?.total_shares ?? '—'}
icon={<Share2 size={13} className="text-zinc-500" />}
/>
<StatCard
label="Link clicks"
value={adminStats?.total_views ?? '—'}
icon={<MousePointerClick size={13} className="text-zinc-500" />}
/>
</div>
</section> </section>
<div className="border-t border-zinc-800" /> <div className="border-t border-zinc-800" />
@@ -130,9 +144,10 @@ export function SettingsModal({ onClose }: Props) {
); );
} }
function StatCard({ label, value }: { label: string; value: number | string }) { function StatCard({ label, value, icon }: { label: string; value: number | string; icon?: React.ReactNode }) {
return ( return (
<div className="bg-zinc-800/60 rounded-lg px-3 py-3 text-center"> <div className="bg-zinc-800/60 rounded-lg px-3 py-3 text-center">
{icon && <div className="flex justify-center mb-1">{icon}</div>}
<div className="text-lg font-semibold text-zinc-200">{value}</div> <div className="text-lg font-semibold text-zinc-200">{value}</div>
<div className="text-xs text-zinc-500 mt-0.5">{label}</div> <div className="text-xs text-zinc-500 mt-0.5">{label}</div>
</div> </div>

View File

@@ -106,6 +106,14 @@ export function useDeleteMeme() {
}); });
} }
export function useAdminStats() {
return useQuery({
queryKey: ['admin', 'stats'],
queryFn: () => api.admin.stats(),
staleTime: 30_000,
});
}
export function useReindexStatus() { export function useReindexStatus() {
return useQuery({ return useQuery({
queryKey: ['admin', 'reindex-status'], queryKey: ['admin', 'reindex-status'],