Skip to content

Commit

Permalink
feat: integrate caching for blog posts and enhance analytics tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
v0id-user committed Jan 16, 2025
1 parent 57053eb commit 610ef87
Show file tree
Hide file tree
Showing 23 changed files with 928 additions and 114 deletions.
6 changes: 0 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# GPG Keys
keys/*

# ????
app/w/w/*
22 changes: 16 additions & 6 deletions app/api/blog/draft/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
import { getSpacerToken } from "@/lib/token";
import { updateBlogPostDraftUnpublished, getPost, initBlogPostDraft } from "@/lib/blog";
import { updateBlogPostDraftUnpublished, getPostNoCache, initBlogPostDraft } from "@/lib/blog";
import { PostStatus } from "@prisma/client";
import { BlogPostRequest } from "@/interfaces/blog";

export const maxDuration = 30; // 30 seconds timeout
export const maxDuration = 30;

interface CategoryLike {
name: string;
}

type CategoryInput = string | CategoryLike;

export async function POST(request: NextRequest) {
console.log('Received POST request for draft creation or update.');
Expand All @@ -22,7 +28,12 @@ export async function POST(request: NextRequest) {
const blogPostData: BlogPostRequest = data as BlogPostRequest;
// Normalize categories to lowercase if present
if (blogPostData.categories) {
blogPostData.categories = [...new Set(blogPostData.categories.map((cat: string) => cat.toLowerCase()))];
blogPostData.categories = [...new Set(blogPostData.categories.map((cat: CategoryInput) => {
if (typeof cat === 'string') {
return cat.toLowerCase();
}
return cat.name.toLowerCase();
}))];
console.log('Normalized categories:', blogPostData.categories);
}

Expand All @@ -36,7 +47,7 @@ export async function POST(request: NextRequest) {

// Otherwise, update existing post
console.log('Fetching existing post with ID:', data.id);
const existingPost = await getPost(data.id);
const existingPost = await getPostNoCache(data.id);
if (!existingPost) {
console.log('Post not found for ID:', data.id);
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
Expand All @@ -60,7 +71,6 @@ export async function POST(request: NextRequest) {
}
}


// /api/blog?id=123
export async function GET(request: Request) {
const token = await getSpacerToken();
Expand All @@ -73,6 +83,6 @@ export async function GET(request: Request) {
if (!id) {
return NextResponse.json({ error: "No id provided" }, { status: 400 })
}
const draft = await getPost(id)
const draft = await getPostNoCache(id)
return NextResponse.json(draft)
}
4 changes: 1 addition & 3 deletions app/api/blog/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { NextResponse } from 'next/server';
import { getPostPublished, getPublishedPosts } from '@/lib/blog';

export const maxDuration = 30; // 30 seconds timeout

export const maxDuration = 30;
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url)
Expand Down
4 changes: 1 addition & 3 deletions app/api/s3/presign/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { getSpacerToken } from "@/lib/token";
import { S3Client, PutObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { NextResponse } from "next/server";

export const maxDuration = 30; // 30 seconds timeout

export const maxDuration = 30;
// Initialize S3 client with standard AWS configuration
const s3Client = new S3Client({
region: process.env.AWS_REGION || 'auto',
Expand Down
2 changes: 1 addition & 1 deletion app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ export default async function Blog({ params }: Props) {
</div>
</main>
);
}
}
85 changes: 26 additions & 59 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,31 @@ import { useEffect, useState } from 'react'
import { motion } from 'motion/react'
import Link from 'next/link'
import { apiClient } from '@/lib/client'

interface Post {
id: string
title: string
slug: string
createdAt: string
author: {
name: string
}
categories: {
name: string
}[]
}
import BlogCard, { BlogCardSkeleton } from '@/components/BlogCard'
import { BlogCard as BlogCardType } from '@/interfaces/blog'
import { getBlogListCache, setBlogListCache } from '@/lib/cache/blog/client'

export default function Blog() {
const [posts, setPosts] = useState<Post[]>([])
const [posts, setPosts] = useState<BlogCardType[]>([])
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
const fetchPosts = async () => {
try {
// Try to get from cache first
const cachedPosts = getBlogListCache()
if (cachedPosts) {
setPosts(cachedPosts)
setIsLoading(false)
return
}

// If not in cache, fetch from API
const response = await apiClient.get('/blog')
setPosts(response.data)

// Save to cache
setBlogListCache(response.data)
} catch (error) {
console.error('Failed to fetch posts:', error)
} finally {
Expand All @@ -37,14 +39,6 @@ export default function Blog() {
fetchPosts()
}, [])

if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-gray-900"></div>
</div>
)
}

return (
<main className="items-center pt-40 sm:pt-52 pb-24 min-h-screen" dir='rtl'>
{/* Navigation */}
Expand Down Expand Up @@ -75,43 +69,16 @@ export default function Blog() {
transition={{ delay: 0.2, duration: 0.5 }}
className="space-y-8"
>
{posts.map((post, index) => (
<motion.article
key={post.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 * index, duration: 0.5 }}
className="border-b border-gray-200 pb-8 last:border-0"
>
<Link href={`/blog/${post.slug}`} className="group">
{/* Post Title */}
<h2 className="text-2xl font-semibold mb-2 group-hover:text-gray-600 transition-colors">
{post.title}
</h2>

{/* Post Metadata */}
<div className="flex items-center text-sm text-gray-500 gap-4">
<span>{post.author.name}</span>
<span></span>
<span>{new Date(post.createdAt).toLocaleDateString('ar-SA')}</span>
</div>

{/* Categories */}
{post.categories.length > 0 && (
<div className="flex gap-2 mt-3">
{post.categories.map(category => (
<span
key={category.name}
className="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded"
>
{category.name}
</span>
))}
</div>
)}
</Link>
</motion.article>
))}
{isLoading ? (
// Show skeleton loading state
Array.from({ length: 3 }).map((_, index) => (
<BlogCardSkeleton key={index} />
))
) : (
posts.map((post, index) => (
<BlogCard key={post.id} post={post} index={index} />
))
)}
</motion.div>
</section>
</div>
Expand Down
25 changes: 21 additions & 4 deletions app/blog/verify/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { Post, User } from "@prisma/client";
import { getPostPublished } from "@/lib/client/blog";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import React from "react";
import Loader from "@/components/Loader";
import { setCache, getCache } from "@/lib/client/cache"; // Import cache functions

interface ExtendedPost extends Post {
author: User;
Expand All @@ -18,17 +19,33 @@ type Props = {

export default function Blog({ params }: Props) {
const [post, setPost] = useState<ExtendedPost | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchPost = async () => {
const id = (await params).id;
const post = await getPostPublished(id) as ExtendedPost | null;
console.log(post);
setPost(post);
const cachedPost = getCache<ExtendedPost>(`post_${id}`); // Try to get from cache

if (cachedPost) {
setPost(cachedPost); // Set post from cache
setLoading(false);
} else {
const post = await getPostPublished(id) as ExtendedPost | null;
console.log(post);
if (post) {
setPost(post);
setCache(`post_${id}`, post); // Cache the fetched post
}
setLoading(false);
}
}
fetchPost();
}, [params])

if (loading) {
return <Loader />
}

return (
<main>
<div className="min-h-screen text-right">
Expand Down
5 changes: 5 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import "./globals.css";
import Cursor from "@/components/Cursor";
import { Toaster } from "react-hot-toast";
import BannerMessage from "@/components/BannerMessage";
import { Analytics } from "@vercel/analytics/react"

const ibmPlexSansArabic = IBM_Plex_Sans_Arabic({
weight: ["100", "200", "300", "400", "500", "600", "700"],
subsets: ["arabic"],
Expand All @@ -29,6 +31,9 @@ export default function RootLayout({
return (
<html lang="en">
<body className={`${ibmPlexSansArabic.className} antialiased`}>
{/* Global Analytics */}
<Analytics />

{/* Global Cursor */}
<Cursor />

Expand Down
Loading

0 comments on commit 610ef87

Please sign in to comment.