From 331ac06f985b9e9eea2bc7375e49dbfcc4e92811 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sun, 4 Oct 2026 10:05:40 +0530 Subject: [PATCH] Remove obsolete backend helpers and frontend compatibility code --- .../organisms/overview-breakdown-charts.tsx | 86 +------------------ .../components/organisms/overview-panels.tsx | 13 --- apps/admin/src/lib/resources.ts | 3 - apps/admin/src/lib/server/session.ts | 22 +---- apps/extensions/README.md | 5 +- apps/extensions/src/entry.tsx | 17 +--- apps/extensions/tests/reader.browser.mjs | 26 ++++++ apps/web/src/lib/mcp.ts | 3 - packages/core/src/job_retries.py | 9 +- packages/core/src/schemas.py | 7 -- packages/core/src/topic_corrections.py | 82 +----------------- 11 files changed, 37 insertions(+), 236 deletions(-) diff --git a/apps/admin/src/components/organisms/overview-breakdown-charts.tsx b/apps/admin/src/components/organisms/overview-breakdown-charts.tsx index 00e6d06db..d4c4d8b31 100644 --- a/apps/admin/src/components/organisms/overview-breakdown-charts.tsx +++ b/apps/admin/src/components/organisms/overview-breakdown-charts.tsx @@ -1,18 +1,7 @@ "use client"; -import { - Bar, - BarChart, - CartesianGrid, - Cell, - Scatter, - ScatterChart, - Tooltip, - XAxis, - YAxis, - ZAxis, -} from "recharts"; -import { ChartContainer, ChartTooltip } from "@/components/atoms/chart"; +import { CartesianGrid, Cell, Scatter, ScatterChart, Tooltip, XAxis, YAxis, ZAxis } from "recharts"; +import { ChartContainer } from "@/components/atoms/chart"; export type DistributionRow = { label: string; value: number; color: string; detail?: string }; export function DistributionChart({ @@ -186,74 +175,3 @@ export function CoverageChart({ ); } - -export function JobOutcomesChart({ - rows, -}: { - rows: { name: string; completed: number; failed: number }[]; -}) { - const active = rows.filter((row) => row.completed + row.failed > 0); - if (!active.length) - return ( -

- No completed or failed jobs in this period. -

- ); - return ( - <> -
- - - Completed - - - - Failed - -
- - - - `${Math.round(value * 100)}%`} - tickLine={false} - axisLine={false} - /> - - } /> - - - - - - ); -} diff --git a/apps/admin/src/components/organisms/overview-panels.tsx b/apps/admin/src/components/organisms/overview-panels.tsx index 4e1d942ea..08eaa802a 100644 --- a/apps/admin/src/components/organisms/overview-panels.tsx +++ b/apps/admin/src/components/organisms/overview-panels.tsx @@ -539,19 +539,6 @@ export function OverviewProcessing({ data }: { data: AdminOverview }) { } export type OverviewSection = "sources" | "personalization" | "operations"; -export function OverviewDetails({ data }: { data: AdminOverview }) { - return ( -
-
- - -
- - - -
- ); -} export function OverviewJobReliability({ data }: { data: AdminOverview }) { const rows = (data.insights?.processing ?? []) diff --git a/apps/admin/src/lib/resources.ts b/apps/admin/src/lib/resources.ts index dc0fc8df1..b17d3e74e 100644 --- a/apps/admin/src/lib/resources.ts +++ b/apps/admin/src/lib/resources.ts @@ -461,9 +461,6 @@ export const resources: Record = { }, }; export const resourceKeys = Object.keys(resources) as Resource[]; -export function isResource(value: string): value is Resource { - return Object.hasOwn(resources, value); -} export function humanize(value: string) { return value.replaceAll("_", " ").replace(/^./, (char) => char.toUpperCase()); } diff --git a/apps/admin/src/lib/server/session.ts b/apps/admin/src/lib/server/session.ts index 7ec7fdf1f..176536924 100644 --- a/apps/admin/src/lib/server/session.ts +++ b/apps/admin/src/lib/server/session.ts @@ -3,12 +3,7 @@ import "server-only"; import { cache } from "react"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; -import type { - AdminIdentity, - AdminOverview, - AuthConfig, - UserSettings, -} from "@/lib/api/generated/models"; +import type { AdminIdentity, AuthConfig, UserSettings } from "@/lib/api/generated/models"; import { adminApiOrigin } from "./config"; export async function currentAdmin(): Promise { @@ -43,21 +38,6 @@ export async function authConfiguration(): Promise { return response.json() as Promise; } -export async function initialOverview(days = 30): Promise { - const jar = await cookies(); - const session = jar.get("__Host-devfeed_admin_session") ?? jar.get("devfeed_admin_session"); - if (!session) redirect("/login"); - const response = await fetch(`${adminApiOrigin()}/v1/admin/overview?days=${days}`, { - headers: { Cookie: `${session.name}=${session.value}`, ...traceHeaders() }, - cache: "no-store", - redirect: "error", - signal: AbortSignal.timeout(10_000), - }); - if (response.status === 401 || response.status === 403) redirect("/login"); - if (!response.ok) throw new Error("Admin service unavailable"); - return response.json() as Promise; -} - export const initialUserSettings = cache(async (): Promise => { const jar = await cookies(); const session = jar.get("__Host-devfeed_admin_session") ?? jar.get("devfeed_admin_session"); diff --git a/apps/extensions/README.md b/apps/extensions/README.md index e667ccb96..a6d1fa91b 100644 --- a/apps/extensions/README.md +++ b/apps/extensions/README.md @@ -102,8 +102,9 @@ cache of public article/topic records survives same-tab reloads; it contains no sessions, CSRF tokens, recommendation reasons, or personal engagement state. Snapshots expire after 24 hours. Storage restrictions can prevent reload recovery. -The new `/api/v1/feed/options` route provides contextual filter choices. Older -deployments fall back to the source catalog and standard content/language choices. +The `/api/v1/feed/options` route provides contextual filter choices and is required +for extension feed filters. If options are unavailable, successfully loaded articles +remain readable, and refreshing the tab restores filters once the route recovers. Topics and sources use the existing paginated public catalog endpoints. ## Reader parity diff --git a/apps/extensions/src/entry.tsx b/apps/extensions/src/entry.tsx index 8bb0d0564..7ba9e702d 100644 --- a/apps/extensions/src/entry.tsx +++ b/apps/extensions/src/entry.tsx @@ -19,12 +19,7 @@ import { import { SearchFilters } from "../../web/src/components/search-filters"; import { LoadingSkeleton } from "../../web/src/components/loading-skeleton"; import { configureReaderRuntime, readerRequest } from "../../web/src/lib/reader-runtime"; -import { - contentTypes, - feedParams, - latestFeedParams, - parseFilters, -} from "../../web/src/lib/feed-query"; +import { feedParams, latestFeedParams, parseFilters } from "../../web/src/lib/feed-query"; import { parseSearchOptions, normalizeSearch, type SearchResponse } from "../../web/src/lib/search"; import type { FeedPage, FeedOptions, Topic, Source } from "../../web/src/lib/types"; import { createReaderTransport, publicOrigin } from "./transport"; @@ -155,15 +150,7 @@ function Reader({ const params = feedParams(resolvedFilters); const [feed, options] = await Promise.allSettled([ read(`/api/v1/feed?${latestFeedParams(resolvedFilters)}`, signal), - read(`/api/v1/feed/options?${params}`, signal).catch(async () => { - // Compatibility with deployments predating the public options route. - const { items: sources } = await read<{ items: Source[] }>("/api/v1/sources", signal); - return { - sources, - content_types: [...contentTypes], - languages: ["en", "es", "fr", "de", "pt", "ja", "zh", "ko", "hi"], - }; - }), + read(`/api/v1/feed/options?${params}`, signal), ]); if (!signal.aborted) setState({ diff --git a/apps/extensions/tests/reader.browser.mjs b/apps/extensions/tests/reader.browser.mjs index 1fb816e90..c26d397bf 100644 --- a/apps/extensions/tests/reader.browser.mjs +++ b/apps/extensions/tests/reader.browser.mjs @@ -89,6 +89,7 @@ test( let engagementPagination = false; let failNextPage = true; let failArticle = true; + let feedOptionsStatus = 200; await context.route("https://identity.example/authorize?**", (route) => route.fulfill({ contentType: "text/html", body: "

Sign-in provider

" }), ); @@ -185,6 +186,8 @@ test( } else if (url.pathname === "/api/v1/user/auth/me") { json = null; } else if (url.pathname === "/api/v1/feed/options") { + if (feedOptionsStatus !== 200) + return route.fulfill({ status: feedOptionsStatus, json: {} }); json = { sources: article.sources, content_types: ["article", "news", "tutorial", "release", "comparison", "opinion"], @@ -255,6 +258,29 @@ test( await checkArticleGrid(page, path.join(extension, "../grid-" + browser)); assert.ok(page.url().startsWith("chrome-extension://")); await page.waitForURL(/#\/latest$/); + for (const status of [404, 503]) { + feedOptionsStatus = status; + const sourceRequests = requests.filter((url) => url.pathname === "/api/v1/sources").length; + const optionsResponse = page.waitForResponse( + (response) => new URL(response.url()).pathname === "/api/v1/feed/options", + ); + await page.reload(); + assert.equal((await optionsResponse).status(), status); + await page.locator(".article-card").first().waitFor(); + assert.deepEqual(await page.locator(".feed-toolbar a").allTextContents(), ["All"]); + assert.equal( + requests.filter((url) => url.pathname === "/api/v1/sources").length, + sourceRequests, + "Unavailable feed options must not trigger a broad source-catalog fallback", + ); + } + feedOptionsStatus = 200; + await page.reload(); + await page + .locator(".feed-toolbar") + .getByRole("link", { name: "News", exact: true }) + .waitFor(); + await page.locator(".article-card").first().waitFor(); const publicProfile = await context.newPage(); await publicProfile.goto(page.url().split("#")[0] + "#/users/reader"); await publicProfile.getByRole("heading", { name: "Reader Profile" }).waitFor(); diff --git a/apps/web/src/lib/mcp.ts b/apps/web/src/lib/mcp.ts index 94ee45b1c..66b9ef3ef 100644 --- a/apps/web/src/lib/mcp.ts +++ b/apps/web/src/lib/mcp.ts @@ -1,8 +1,5 @@ export type McpClient = "vscode" | "codex" | "claude" | "cursor" | "other"; -export const mcpServerGuide = - "https://github.com/abhi1693/devfeed.tech/blob/master/apps/mcp/README.md"; - export const mcpClients: Record< McpClient, { diff --git a/packages/core/src/job_retries.py b/packages/core/src/job_retries.py index 5cd942000..fe21d8818 100644 --- a/packages/core/src/job_retries.py +++ b/packages/core/src/job_retries.py @@ -2,7 +2,7 @@ from datetime import timedelta -from sqlalchemy import and_, case, or_, select, tuple_ +from sqlalchemy import and_, or_, select, tuple_ from sqlalchemy.orm import aliased from devfeed_core.analysis import request_analysis @@ -59,13 +59,6 @@ def retry_candidate(model): return and_(failed, ~superseded) -def job_display_status(model): - return case( - (and_(model.status == "failed", ~retry_candidate(model)), "retried"), - else_=model.status, - ) - - def retry_notification(session, identifier): job = session.scalar( select(NotificationDelivery) diff --git a/packages/core/src/schemas.py b/packages/core/src/schemas.py index 7ad47fe9d..32e90ce58 100644 --- a/packages/core/src/schemas.py +++ b/packages/core/src/schemas.py @@ -191,13 +191,6 @@ class SourcePublicOut(SourceRef): created_at: datetime -class SourceSubmissionOut(SourceRef): - feed_url: str - approval_status: ApprovalStatus - submitted_by: SourceSubmitterOut | None - created_at: datetime - - class ImageJobOut(ORMModel): id: uuid.UUID article_id: uuid.UUID | None diff --git a/packages/core/src/topic_corrections.py b/packages/core/src/topic_corrections.py index f62159ec4..f1ff26f70 100644 --- a/packages/core/src/topic_corrections.py +++ b/packages/core/src/topic_corrections.py @@ -2,94 +2,16 @@ import uuid -from sqlalchemy import or_, select +from sqlalchemy import select from devfeed_core.analysis import snapshot_hash -from devfeed_core.models import ArticleTopic, Tag, Topic, TopicProposal, TopicRelation, utcnow +from devfeed_core.models import Topic, TopicProposal from devfeed_core.services import OperationConflict, RecordNotFound from devfeed_core.topic_auto_approval import ACTOR from devfeed_core.topic_proposals import TopicReview, review_proposal, snapshot from devfeed_core.topics import TopicWrite, lock_topics -def retract_automatic_topic(session, identifier, *, expected_hash, note, actor): - """Retire an unchanged automatic approval after its links have been reconciled. - - Retain the rejected identity so imports cannot silently recreate it, and retain - the complete previous review in the proposal's correction evidence. - """ - if not note.strip() or len(note) > 1000: - raise ValueError("A bounded correction reason is required") - lock_topics(session) - topic = session.scalar( - select(Topic) - .where(Topic.id == identifier) - .with_for_update(nowait=True) - .execution_options(populate_existing=True) - ) - if topic is None: - raise RecordNotFound("Topic not found") - before = snapshot(topic) - if topic.status != "active" or snapshot_hash(before) != expected_hash: - raise OperationConflict("Topic changed before retraction") - previous = session.scalar( - select(TopicProposal) - .where(TopicProposal.topic_id == identifier, TopicProposal.status == "approved") - .order_by(TopicProposal.reviewed_at.desc(), TopicProposal.id.desc()) - .limit(1) - .with_for_update(nowait=True) - ) - if ( - previous is None - or previous.reviewed_at is None - or previous.reviewed_by != ACTOR - or previous.applied != before - ): - raise OperationConflict("Only an unchanged automatic approval can be retracted") - if ( - session.scalar(select(ArticleTopic.article_id).where(ArticleTopic.topic_id == identifier)) - or session.scalar(select(Tag.id).where(Tag.topic_id == identifier)) - or session.scalar( - select(TopicRelation.topic_id).where( - or_( - TopicRelation.topic_id == identifier, - TopicRelation.related_topic_id == identifier, - ) - ) - ) - or session.scalar( - select(TopicProposal.id).where( - TopicProposal.topic_id == identifier, TopicProposal.status == "pending" - ) - ) - ): - raise OperationConflict( - "Reconcile article, tag, relationship and pending proposal links first" - ) - now = utcnow() - previous.evidence = [ - *previous.evidence, - { - "provider": "authorized_topic_retraction", - "actor": actor, - "reason": note, - "corrected_at": now.isoformat(), - "previous_status": previous.status, - "previous_reviewed_at": previous.reviewed_at.isoformat(), - "previous_reviewed_by": previous.reviewed_by, - "previous_review_note": previous.review_note, - "previous_snapshot": before, - }, - ] - previous.status, previous.reviewed_at, previous.reviewed_by = "rejected", now, actor - previous.review_note = note - topic.status = "rejected" - from devfeed_core.tag_topic_discovery import catalog_changed - - catalog_changed(session) - session.flush() - - def correct_automatic_topic(session, identifier, *, expected_hash, body: TopicWrite, note, actor): """Caller supplies an explicitly reviewed replacement and expected current snapshot.""" if not note.strip() or len(note) > 1000: