From 98e5a5a740371422087beee6b6f6557e9df6638e Mon Sep 17 00:00:00 2001 From: jason Date: Sat, 28 Mar 2026 22:09:30 -0500 Subject: [PATCH] build view count --- backend/src/db.ts | 4 ++++ backend/src/routes/share.ts | 3 +++ backend/src/types.ts | 1 + frontend/src/api/client.ts | 1 + frontend/src/components/MemeDetail.tsx | 13 +++++++------ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/src/db.ts b/backend/src/db.ts index 3a8068f..0f0224c 100644 --- a/backend/src/db.ts +++ b/backend/src/db.ts @@ -70,6 +70,10 @@ if (!memesCols.find((c) => c.name === 'share_count')) { db.exec('ALTER TABLE memes ADD COLUMN share_count INTEGER NOT NULL DEFAULT 0'); } +if (!memesCols.find((c) => c.name === 'view_count')) { + db.exec('ALTER TABLE memes ADD COLUMN view_count INTEGER NOT NULL DEFAULT 0'); +} + // Indexes that depend on migrated columns — created after columns are guaranteed to exist db.exec(` CREATE INDEX IF NOT EXISTS idx_memes_collection_id ON memes(collection_id); diff --git a/backend/src/routes/share.ts b/backend/src/routes/share.ts index 9c0fa84..5849a50 100644 --- a/backend/src/routes/share.ts +++ b/backend/src/routes/share.ts @@ -31,6 +31,9 @@ export async function shareRoutes(app: FastifyInstance) { return reply.status(404).send('Not found'); } + // Count every visit to the share page as a link click + db.prepare('UPDATE memes SET view_count = view_count + 1 WHERE id = ?').run(meme.id); + const base = getBaseUrl(req as any); const pageUrl = `${base}/m/${meme.id}`; const imageUrl = `${base}/images/${meme.file_path}`; diff --git a/backend/src/types.ts b/backend/src/types.ts index 6c370a0..6e006b2 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -12,6 +12,7 @@ export interface Meme { collection_id: number | null; ocr_text: string | null; share_count: number; + view_count: number; created_at: string; tags: string[]; } diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 7ddcfc4..126185f 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -12,6 +12,7 @@ export interface Meme { collection_id: number | null; ocr_text: string | null; share_count: number; + view_count: number; created_at: string; tags: string[]; children?: Meme[]; diff --git a/frontend/src/components/MemeDetail.tsx b/frontend/src/components/MemeDetail.tsx index 569f2c3..b144dab 100644 --- a/frontend/src/components/MemeDetail.tsx +++ b/frontend/src/components/MemeDetail.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { X, Minimize2, Trash2, Edit2, Check, Layers, FolderOpen, Inbox, ScanText, ChevronDown, ChevronUp, ExternalLink, Image, Info, Share2 } from 'lucide-react'; +import { X, Minimize2, Trash2, Edit2, Check, Layers, FolderOpen, Inbox, ScanText, ChevronDown, ChevronUp, ExternalLink, Image, Info, Share2, MousePointerClick } from 'lucide-react'; import { useMeme, useDeleteMeme, useUpdateMeme, useMoveMeme, useCollections } from '../hooks/useMemes'; import { useAuth } from '../hooks/useAuth'; import { SharePanel } from './SharePanel'; @@ -334,11 +334,12 @@ export function MemeDetail({ memeId, onClose }: Props) {
{meme.mime_type.replace('image/', '').replace('video/', '')}
-
Shared
-
- - {meme.share_count ?? 0} time{(meme.share_count ?? 0) !== 1 ? 's' : ''} -
+
Shared
+
{meme.share_count ?? 0} time{(meme.share_count ?? 0) !== 1 ? 's' : ''}
+
+
+
Link clicks
+
{meme.view_count ?? 0}
Uploaded