phase 1-2 done

This commit is contained in:
Ali Taghavi
2026-04-24 03:42:34 +03:30
commit 95a546df11
83 changed files with 17168 additions and 0 deletions

90
src/app/api/og/route.tsx Normal file
View File

@@ -0,0 +1,90 @@
import { ImageResponse } from "next/og";
import type { NextRequest } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const title = searchParams.get("title") ?? "Ali Taghavi";
const category = searchParams.get("category") ?? "";
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "flex-end",
padding: "60px",
background: "#0A0A0B",
fontFamily: "sans-serif",
position: "relative",
}}
>
{/* Dot grid */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.04) 1px, transparent 1px)",
backgroundSize: "28px 28px",
}}
/>
{/* Accent glow */}
<div
style={{
position: "absolute",
top: "-100px",
right: "-100px",
width: "400px",
height: "400px",
borderRadius: "50%",
background: "rgba(204,253,101,0.07)",
filter: "blur(60px)",
}}
/>
{/* Category label */}
{category && (
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
marginBottom: "20px",
}}
>
<span style={{ color: "#CCFD65", fontSize: "14px", fontFamily: "monospace" }}>
// {category}
</span>
</div>
)}
{/* Title */}
<div
style={{
fontSize: title.length > 60 ? "36px" : "48px",
fontWeight: "700",
color: "#FAFAFA",
lineHeight: 1.3,
maxWidth: "900px",
marginBottom: "32px",
}}
>
{title}
</div>
{/* Footer */}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<span style={{ color: "#CCFD65", fontSize: "18px", fontFamily: "monospace", fontWeight: "700" }}>
// ali taghavi
</span>
<span style={{ color: "#A1A1AA", fontSize: "14px" }}>biztaghavi.com</span>
</div>
</div>
),
{ width: 1200, height: 630 }
);
}

View File

@@ -0,0 +1,13 @@
import { revalidateTag } from "next/cache";
import type { NextRequest } from "next/server";
export async function POST(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");
if (secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ message: "Invalid secret" }, { status: 401 });
}
revalidateTag("sanity", "default");
return Response.json({ revalidated: true, now: Date.now() });
}

45
src/app/api/rss/route.ts Normal file
View File

@@ -0,0 +1,45 @@
import { sanityFetch } from "@/lib/sanity/client";
import { rssPostsQuery } from "@/lib/sanity/queries";
const SITE_URL = "https://biztaghavi.com";
export async function GET() {
const posts = await sanityFetch<
Array<{ _id: string; title: string; slug: { current: string }; excerpt?: string; publishedAt: string; category: string }>
>(rssPostsQuery);
const items = (posts ?? [])
.map((post) => {
const url = `${SITE_URL}/writing/${post.slug.current}`;
const date = post.publishedAt ? new Date(post.publishedAt).toUTCString() : "";
return `
<item>
<title><![CDATA[${post.title}]]></title>
<link>${url}</link>
<guid>${url}</guid>
<pubDate>${date}</pubDate>
<category>${post.category}</category>
${post.excerpt ? `<description><![CDATA[${post.excerpt}]]></description>` : ""}
</item>`;
})
.join("\n");
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>علی تقوی — نوشته‌ها</title>
<link>${SITE_URL}</link>
<description>یادداشت‌ها، تحلیل‌ها و آموخته‌هایم از ساختن کسب‌وکار، محصول و برند</description>
<language>fa</language>
<atom:link href="${SITE_URL}/api/rss" rel="self" type="application/rss+xml" />
${items}
</channel>
</rss>`;
return new Response(xml, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
"Cache-Control": "public, max-age=3600, s-maxage=3600",
},
});
}