Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ ODDSFRONT_MARKET_FEED_TOKEN=
# Optional server-only DropsTab API credential. The application works without
# it and never sends this value to the browser.
DROPSTAB_API_KEY=

# Optional ownership meta tags for search-engine webmaster consoles. The
# existing Google HTML verification file remains valid; set these only when a
# provider gives the canonical oddsfront.com property a new verification code.
ODDSFRONT_GOOGLE_SITE_VERIFICATION=
ODDSFRONT_BING_SITE_VERIFICATION=
ODDSFRONT_YANDEX_SITE_VERIFICATION=
ODDSFRONT_NAVER_SITE_VERIFICATION=
ODDSFRONT_BAIDU_SITE_VERIFICATION=
16 changes: 2 additions & 14 deletions app/(default)/news-sitemap.xml/route.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import { getNewsCatalog } from "@/lib/news/catalog";
import { articleText } from "@/lib/news/locale";
import { articleLocales, newsArticlePath } from "@/lib/news/routing";
import { newsSitemapIndex } from "@/lib/news/news-sitemap";

export const revalidate = 300;
const xml = (value: string) => value.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;");

export async function GET() {
const catalog = await getNewsCatalog();
const cutoff = Date.now() - 48 * 60 * 60_000;
const urls = catalog.articles
.filter((article) => Date.parse(article.publishedAt) >= cutoff)
.flatMap((article) => articleLocales(article).map((locale) => {
const text = articleText(article, locale);
return `<url><loc>${xml(`https://oddsfront.com${newsArticlePath(article, locale)}`)}</loc><news:news><news:publication><news:name>OddsFront</news:name><news:language>${xml(locale.split("-",1)[0])}</news:language></news:publication><news:publication_date>${xml(article.publishedAt)}</news:publication_date><news:title>${xml(text.title)}</news:title></news:news></url>`;
}))
.slice(0, 1000)
.join("");
return new Response(`<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">${urls}</urlset>`, {
return new Response(newsSitemapIndex(catalog), {
headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=60, s-maxage=300, stale-while-revalidate=3600" },
});
}
17 changes: 17 additions & 0 deletions app/(default)/news-sitemaps/[part]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getNewsCatalog } from "@/lib/news/catalog";
import { newsSitemap, newsSitemapLocale } from "@/lib/news/news-sitemap";

export const revalidate = 300;

export async function GET(
_request: Request,
{ params }: { params: Promise<{ part: string }> },
) {
const { part } = await params;
const match = part.match(/^([a-z]{2}(?:-[a-z]{2})?)-(\d+)\.xml$/i);
const locale = match ? newsSitemapLocale(match[1]) : null;
const body = locale && match ? newsSitemap(await getNewsCatalog(), locale, Number(match[2])) : null;
return body === null
? new Response(null, { status: 404, headers: { "Cache-Control": "no-store" } })
: new Response(body, { headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=60, s-maxage=300, stale-while-revalidate=3600" } });
}
5 changes: 3 additions & 2 deletions app/(default)/news/[country]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { newsOverviewProps } from "@/lib/news/index-page";
import { getNewsArticle, getNewsCatalog } from "@/lib/news/catalog";
import { normalizeLocale } from "@/lib/news/locale";
import { articleMetadata, newsMetadata } from "@/lib/news/metadata";
import { isNewsCountrySegment, newsArticlePath, newsCountryPath } from "@/lib/news/routing";
import { isNewsCountrySegment, newsArticlePath, newsCountryPath, newsPath } from "@/lib/news/routing";
export const revalidate = 60;
type Props = { params: Promise<{ country: string }>; searchParams: Promise<{ lang?: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
Expand All @@ -22,7 +22,8 @@ export default async function DefaultNewsRoute({ params, searchParams }: Props)
if (!article || (locale && locale !== "en" && !article.translations[locale])) notFound();
permanentRedirect(newsArticlePath(article, locale ?? "en"));
}
if (country === "world") permanentRedirect(newsPath(locale ?? "en"));
if (locale && locale !== "en") permanentRedirect(newsCountryPath(country, locale));
const catalog = await getNewsCatalog();
return <NewsOverview {...newsOverviewProps(catalog, { country: country === "world" ? "ALL" : country.toUpperCase() })} activeCountry={country === "world" ? "ALL" : country.toUpperCase()}/>;
return <NewsOverview {...newsOverviewProps(catalog, { country: country.toUpperCase() })} activeCountry={country.toUpperCase()}/>;
}
4 changes: 2 additions & 2 deletions app/(default)/news/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Metadata } from "next";
import { AboutNewsView } from "@/components/news/about";
export const metadata:Metadata={title:"About the newsdesk | OddsFront",alternates:{canonical:"/news/about"}};
import { newsUtilityMetadata } from "@/lib/news/metadata";
export const metadata=newsUtilityMetadata("about");
export default function AboutNews(){return <AboutNewsView/>;}
31 changes: 28 additions & 3 deletions app/(default)/news/archive/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { NewsOverview } from "@/components/news/news";
import { getNewsCatalog } from "@/lib/news/catalog";
import { newsOverviewProps } from "@/lib/news/index-page";
import { NEWS_INDEX_PAGE_SIZE, newsOverviewProps } from "@/lib/news/index-page";
import { newsUtilityMetadata } from "@/lib/news/metadata";
export const revalidate=60;
export const metadata:Metadata={title:"News archive | OddsFront",alternates:{canonical:"/news/archive"}};
export default async function NewsArchive(){const catalog=await getNewsCatalog();return <NewsOverview {...newsOverviewProps(catalog)} activeCountry="ALL" archive/>;}

type ArchivePageProps = { searchParams: Promise<{ page?: string | string[] }> };

function pageNumber(value: string | string[] | undefined): number | null {
const candidate = Array.isArray(value) ? value[0] : value;
if (candidate === undefined) return 1;
if (!/^[1-9]\d*$/.test(candidate)) return null;
const page = Number(candidate);
return Number.isSafeInteger(page) && Number.isSafeInteger(page * NEWS_INDEX_PAGE_SIZE) ? page : null;
}

export async function generateMetadata({ searchParams }: ArchivePageProps): Promise<Metadata> {
const page = pageNumber((await searchParams).page) ?? 1;
return newsUtilityMetadata("archive", page);
}

export default async function NewsArchive({ searchParams }: ArchivePageProps) {
const page = pageNumber((await searchParams).page);
if (page === null) notFound();
const offset = (page - 1) * NEWS_INDEX_PAGE_SIZE;
const catalog = await getNewsCatalog();
const props = newsOverviewProps(catalog, { offset });
if (page > 1 && props.initialArticles.length === 0) notFound();
return <NewsOverview {...props} activeCountry="ALL" archive initialOffset={offset} archivePage={page}/>;
}
5 changes: 3 additions & 2 deletions app/[locale]/news/[country]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getNewsArticle, getNewsCatalog } from "@/lib/news/catalog";
import { normalizeLocale } from "@/lib/news/locale";
import { articleMetadata, newsMetadata } from "@/lib/news/metadata";
import { newsOverviewProps } from "@/lib/news/index-page";
import { isNewsCountrySegment, newsCountryPath } from "@/lib/news/routing";
import { isNewsCountrySegment, newsCountryPath, newsPath } from "@/lib/news/routing";

type Props = { params: Promise<{ locale: string; country: string }> };
export const revalidate = 60;
Expand All @@ -25,7 +25,8 @@ export default async function LocalizedNewsRoute({ params }: Props) {
const locale = normalizeLocale(segment);
if (!locale) notFound();
if (!isNewsCountrySegment(slug)) return <ArticlePageContent slug={slug} locale={locale}/>;
if (slug === "world") permanentRedirect(newsPath(locale));
if (locale === "en") permanentRedirect(newsCountryPath(slug, "en"));
const catalog = await getNewsCatalog();
return <NewsOverview {...newsOverviewProps(catalog, { country: slug === "world" ? "ALL" : slug.toUpperCase() })} activeCountry={slug === "world" ? "ALL" : slug.toUpperCase()}/>;
return <NewsOverview {...newsOverviewProps(catalog, { country: slug.toUpperCase() })} activeCountry={slug.toUpperCase()}/>;
}
1 change: 1 addition & 0 deletions components/news/news.module.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading