add request counting
Some checks failed
ci/woodpecker/push/build Pipeline failed

This commit is contained in:
Jake Walker 2025-07-28 22:12:08 +01:00
parent 44827ef205
commit 776cf139fa
No known key found for this signature in database
7 changed files with 64 additions and 1 deletions

View file

@ -25,4 +25,7 @@ COPY --from=frontend-build /app/dist /opt/dist
ENV PG_DIRECTORY=/work
ENV PG_UPLOADS_DIRECTORY=/uploads
RUN chmod a+x /opt/docker-entrypoint.sh
ENTRYPOINT ["/opt/docker-entrypoint.sh"]
CMD ["uv", "run", "uvicorn", "--app-dir", "/opt/src", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View file

@ -40,6 +40,7 @@ export type PodcastEpisodePublic = {
file_hash: string;
file_size: number;
publish_date?: string;
request_count?: number;
id: string;
podcast_id: string;
description_html: string | null;

View file

@ -1,6 +1,6 @@
import { createMutation, createQuery, useQueryClient, } from "@tanstack/solid-query";
import AdminLayout from "../../admin-layout";
import { createEffect, createSignal, ErrorBoundary, For, Match, Show, Suspense, Switch } from "solid-js";
import { createEffect, createMemo, createSignal, ErrorBoundary, For, Match, Show, Suspense, Switch } from "solid-js";
import { A, useNavigate, useParams } from "@solidjs/router";
import { deletePodcastMutation, readEpisodesOptions, readPodcastOptions, readPodcastQueryKey, readPodcastsQueryKey, updatePodcastMutation } from "../../client/@tanstack/solid-query.gen";
import Error from "../../components/error";
@ -11,6 +11,12 @@ import Upload from "../../components/upload";
import UploadImage from "../../components/upload-image";
import { PodcastPublic } from "../../client";
function calculateEpisodeRequestPercent(episodeRequestCount?: number, totalRequestCount?: number): number {
const totalClipped = (totalRequestCount === 0 ? 1 : totalRequestCount) ?? 1;
const percent = (episodeRequestCount ?? 0) / totalClipped;
return percent;
}
export default function AdminPodcast() {
const params = useParams();
const navigate = useNavigate();
@ -79,6 +85,13 @@ export default function AdminPodcast() {
});
}
const totalRequestCount = createMemo(() => {
if (episodeQuery.isSuccess && episodeQuery.data && Array.isArray(episodeQuery.data)) {
return episodeQuery.data.reduce((sum, episode) => sum + (episode.request_count ?? 0), 0);
}
return 0;
});
return (
<AdminLayout>
<ErrorBoundary fallback={(err, reset) => <Error message={err} reset={reset} />}>
@ -123,6 +136,7 @@ export default function AdminPodcast() {
<th>Name</th>
<th>Published</th>
<th>Duration</th>
<th>Requests</th>
<th>Actions</th>
</tr>
</thead>
@ -133,6 +147,12 @@ export default function AdminPodcast() {
<td>{episode.name}</td>
<td>{episode.publish_date ? (new Date(episode.publish_date)).toLocaleString() : "?"}</td>
<td>{episode.duration ? `${(episode.duration / 60).toFixed(0)}min` : "?"}</td>
<td>
{episode.request_count ? <>
<div class="radial-progress me-1.5" style={`--value:${(calculateEpisodeRequestPercent(episode.request_count, totalRequestCount()) * 100).toFixed(1)}; --size:1rem; --thickness:0.2rem;`} aria-valuenow={(calculateEpisodeRequestPercent(episode.request_count, totalRequestCount()) * 100).toFixed(1)} role="progressbar"></div>
<span>{episode.request_count.toLocaleString()} ({(calculateEpisodeRequestPercent(episode.request_count, totalRequestCount()) * 100).toFixed(1)}%)</span>
</> : "-"}
</td>
<td>
<A href={`/admin/${params.podcastId}/${episode.id}`} class="btn btn-ghost btn-xs"><Pencil class="size-4" /> Edit</A>
</td>

5
docker-entrypoint.sh Normal file
View file

@ -0,0 +1,5 @@
#!/bin/sh
set -x
uv run alembic upgrade head || exit 1
exec "$@"

View file

@ -0,0 +1,29 @@
"""add request_count to episode
Revision ID: d58d2db9cd60
Revises: 9efcecc1e58d
Create Date: 2025-07-28 21:59:25.547437
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "d58d2db9cd60"
down_revision: Union[str, None] = "9efcecc1e58d"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
"podcastepisode",
sa.Column("request_count", sa.Integer(), nullable=False, server_default="0"),
)
def downgrade() -> None:
op.drop_column("podcastepisode", "request_count")

View file

@ -482,6 +482,10 @@ def get_episode_or_cover(session: SessionDep, podcast_id: str, filename: str):
if episode is None:
raise HTTPException(status_code=404, detail="Episode or podcast not found")
# increment request count
episode.request_count += 1
session.add(episode)
return FileResponse(settings.directory / f"{episode.id}.m4a")
elif (

View file

@ -60,6 +60,7 @@ class PodcastEpisodeBase(SQLModel):
publish_date: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc), nullable=False
)
request_count: int = Field(default=0, nullable=False)
class PodcastEpisode(PodcastEpisodeBase, table=True):