diff --git a/backend/coreapp/urls.py b/backend/coreapp/urls.py index 01507e2..c57c47e 100644 --- a/backend/coreapp/urls.py +++ b/backend/coreapp/urls.py @@ -12,6 +12,7 @@ from coreapp.views import ( scratch, user, search, + scratch_count, ) router = DefaultRouter(trailing_slash=False) @@ -39,6 +40,9 @@ urlpatterns = [ platform.single_platform, name="platform-detail", ), + path( + "scratch-count", scratch_count.ScratchCountView.as_view(), name="scratch-count" + ), path("stats", stats.StatsDetail.as_view(), name="stats"), path("user", user.CurrentUser.as_view(), name="current-user"), path( diff --git a/backend/coreapp/views/compiler.py b/backend/coreapp/views/compiler.py index 51583c0..5a4f48a 100644 --- a/backend/coreapp/views/compiler.py +++ b/backend/coreapp/views/compiler.py @@ -73,15 +73,11 @@ class CompilerDetail(APIView): } @staticmethod - def platforms_json( - include_num_scratches: bool = False, - ) -> Dict[str, Dict[str, object]]: + def platforms_json() -> Dict[str, Dict[str, object]]: ret: Dict[str, Dict[str, object]] = {} for platform in compilers.available_platforms(): - ret[platform.id] = platform.to_json( - include_num_scratches=include_num_scratches, - ) + ret[platform.id] = platform.to_json() return ret diff --git a/backend/coreapp/views/platform.py b/backend/coreapp/views/platform.py index a121578..945bbba 100644 --- a/backend/coreapp/views/platform.py +++ b/backend/coreapp/views/platform.py @@ -32,7 +32,7 @@ class PlatformDetail(APIView): @condition(last_modified_func=endpoint_updated) def get(self, request: Request) -> Response: - return Response(CompilerDetail.platforms_json(include_num_scratches=False)) + return Response(CompilerDetail.platforms_json()) @api_view(["GET"]) @@ -48,7 +48,6 @@ def single_platform(request: Request, id: str) -> Response: return Response( platform.to_json( include_compilers=True, - include_num_scratches=True, ) ) diff --git a/backend/coreapp/views/scratch_count.py b/backend/coreapp/views/scratch_count.py new file mode 100644 index 0000000..d25d269 --- /dev/null +++ b/backend/coreapp/views/scratch_count.py @@ -0,0 +1,47 @@ +from django.utils.decorators import method_decorator +from django.utils.http import http_date, parse_http_date_safe +from django.utils.timezone import now + +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..decorators.cache import globally_cacheable +from ..models.scratch import Scratch + + +@method_decorator( + globally_cacheable(max_age=60, stale_while_revalidate=30), name="dispatch" +) +class ScratchCountView(APIView): + def get(self, request: Request) -> Response: + qs = Scratch.objects.all() + + platform = request.query_params.get("platform") + compiler = request.query_params.get("compiler") + preset = request.query_params.get("preset") + + if platform: + qs = qs.filter(platform=platform) + if compiler: + qs = qs.filter(compiler=compiler) + if preset: + qs = qs.filter(preset_id=preset) + + latest_created = ( + qs.order_by("-creation_time") + .values_list("creation_time", flat=True) + .first() + ) + if latest_created is None: + latest_created = now() + + if_modified_since = request.headers.get("If-Modified-Since") + if if_modified_since: + since_ts = parse_http_date_safe(if_modified_since) + if since_ts and latest_created.timestamp() <= since_ts: + return Response(status=304) + + resp = Response({"num_scratches": qs.count()}) + resp["Last-Modified"] = http_date(latest_created.timestamp()) + return resp diff --git a/frontend/src/app/(navfooter)/platform/[id]/page.tsx b/frontend/src/app/(navfooter)/platform/[id]/page.tsx index a1ff055..e55cf0a 100644 --- a/frontend/src/app/(navfooter)/platform/[id]/page.tsx +++ b/frontend/src/app/(navfooter)/platform/[id]/page.tsx @@ -6,13 +6,13 @@ import { PlatformIcon } from "@/components/PlatformSelect/PlatformIcon"; import ScratchList from "@/components/ScratchList"; import { ScratchItemPlatformList } from "@/components/ScratchItem"; import { get } from "@/lib/api/request"; -import type { PlatformMetadata } from "@/lib/api/types"; +import type { PlatformBase, ScratchCount } from "@/lib/api/types"; export async function generateMetadata(props: { params: Promise<{ id: number }>; }): Promise { const params = await props.params; - let platform: PlatformMetadata; + let platform: PlatformBase; try { platform = await get(`/platform/${params.id}`); @@ -24,13 +24,21 @@ export async function generateMetadata(props: { return notFound(); } + let scratch_count: ScratchCount; + try { + scratch_count = await get(`/scratch-count?platform=${params.id}`); + } catch (error) { + console.error(error); + } + let description = "There "; - description += platform.num_scratches === 1 ? "is " : "are "; + description += scratch_count.num_scratches === 1 ? "is " : "are "; description += - platform.num_scratches === 0 + scratch_count.num_scratches === 0 ? "currently no " - : `${platform.num_scratches.toLocaleString("en-US")} `; - description += platform.num_scratches === 1 ? "scratch " : "scratches "; + : `${scratch_count.num_scratches.toLocaleString("en-US")} `; + description += + scratch_count.num_scratches === 1 ? "scratch " : "scratches "; description += "for this platform."; return { @@ -44,7 +52,7 @@ export async function generateMetadata(props: { export default async function Page(props: { params: Promise<{ id: number }> }) { const params = await props.params; - let platform: PlatformMetadata; + let platform: PlatformBase; try { platform = await get(`/platform/${params.id}`); } catch (error) { @@ -55,6 +63,13 @@ export default async function Page(props: { params: Promise<{ id: number }> }) { return notFound(); } + let scratch_count: ScratchCount; + try { + scratch_count = await get(`/scratch-count?platform=${params.id}`); + } catch (error) { + console.error(error); + } + return (
@@ -68,7 +83,7 @@ export default async function Page(props: { params: Promise<{ id: number }> }) { url={`/scratch?platform=${platform.id}&page_size=20`} item={ScratchItemPlatformList} isSortable={true} - title={`Scratches (${platform.num_scratches.toLocaleString("en-US")})`} + title={`Scratches (${scratch_count.num_scratches.toLocaleString("en-US")})`} />
diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index 48344ca..67ecb72 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -145,6 +145,10 @@ export type Preset = TersePreset & { num_scratches: number; }; +export type ScratchCount = { + num_scratches: number; +}; + export type CompilerBase = { id: string; }; @@ -163,10 +167,6 @@ export interface PlatformBase { has_decompiler: boolean; } -export interface PlatformMetadata extends PlatformBase { - num_scratches: number; -} - export interface Platform extends PlatformBase { compilers: string[]; }