Skip to content

Commit

Permalink
feat: enhance blog functionality with GPG signing support and update …
Browse files Browse the repository at this point in the history
…data handling

- Added GPG signing status to blog posts, allowing users to verify post authenticity.
- Updated the data structure for blog posts to include GPG signing information.
- Refactored blog fetching logic to accommodate new data structure and improved caching mechanisms.
- Adjusted BlogCard component to display GPG signing status visually.
- Enhanced error handling for invalid post data during rendering.
  • Loading branch information
v0id-user committed Jan 17, 2025
1 parent 90d0f6c commit e252609
Showing 8 changed files with 116 additions and 26 deletions.
2 changes: 2 additions & 0 deletions app/api/blog/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { getPostPublished, getPublishedPosts } from '@/lib/blog';
export const maxDuration = 30;


export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url)
33 changes: 22 additions & 11 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -3,13 +3,13 @@
import { useEffect, useState } from 'react'
import { motion } from 'motion/react'
import Link from 'next/link'
import { apiClient } from '@/lib/client'
import BlogCard, { BlogCardSkeleton } from '@/components/BlogCard'
import { BlogCard as BlogCardType } from '@/interfaces/blog'
import { BlogsResponse } from '@/interfaces/blog'
import { getBlogListCache, setBlogListCache } from '@/lib/cache/blog/client'
import { getPublishedPosts } from '@/lib/client/blog'

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

useEffect(() => {
@@ -24,11 +24,12 @@ export default function Blog() {
}

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

// Save to cache
setBlogListCache(response.data)
const apiPosts = await getPublishedPosts()
console.log('API Response:', apiPosts)
setPosts(apiPosts)

// Save to cache - extract the BlogCard data from the response
setBlogListCache(apiPosts.map(p => p.post))
} catch (error) {
console.error('Failed to fetch posts:', error)
} finally {
@@ -75,9 +76,19 @@ export default function Blog() {
<BlogCardSkeleton key={index} />
))
) : (
posts.map((post, index) => (
<BlogCard key={post.id} post={post} index={index} />
))
posts.map((blogResponse, index) => {
if (!blogResponse.post) {
console.warn('Invalid post data:', blogResponse)
return null
}
return (
<BlogCard
key={blogResponse.post.id}
post={blogResponse.post}
index={index}
/>
);
})
)}
</motion.div>
</section>
2 changes: 1 addition & 1 deletion app/space/auth/actions.ts
Original file line number Diff line number Diff line change
@@ -231,7 +231,7 @@ export async function register({ email, name, gpgSignature, password }: Register
of this instance of the function. All this async machinery for what? ~62 BYTES?!
We could read those ~62 bytes synchronously, quickly, and no one would care.
Yeah If I was a multi-billion dollar company I would use async for my bloatware cookies
like "google level" cookies of trackers and shit, but not I'm NOT I'm a simple guy
like "google level" cookies of trackers and shit, but not I'm bot I'm a simple guy
who wants a sync function to access cookies to portect his simple blog site.
All love,
21 changes: 20 additions & 1 deletion components/BlogCard.tsx
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@ import { BlogCard as BlogCardType } from '@/interfaces/blog'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import Loader from '@/components/Loader'
import { Shield } from 'lucide-react'
import { ShieldCheck } from 'lucide-react'

interface BlogCardProps {
post: BlogCardType
@@ -57,7 +59,6 @@ export default function BlogCard({ post, index }: BlogCardProps) {
<Loader />
</div>
)}

{/* Post Title */}
<h2 className="text-2xl font-semibold mb-2 group-hover:text-gray-600 transition-colors">
{post.title}
@@ -68,6 +69,24 @@ export default function BlogCard({ post, index }: BlogCardProps) {
<span>{post.author.name}</span>
<span></span>
<span>{new Date(post.createdAt).toLocaleDateString('ar-SA')}</span>
{post.signedWithGPG ? (
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
window.open(`/blog/verify/${post.id}`, '_blank');
}}
className="flex underline items-center gap-1 text-green-600 hover:text-green-700 transition-colors flex-shrink-0 cursor-pointer"
>
<ShieldCheck className="w-3 h-3 sm:w-4 sm:h-4" />
<span>موقع بـ GPG</span>
</div>
) : (
<div className="flex items-center gap-1 text-gray-400 flex-shrink-0">
<Shield className="w-3 h-3 sm:w-4 sm:h-4" />
<span>غير موقع</span>
</div>
)}
</div>

{/* Categories */}
5 changes: 5 additions & 0 deletions interfaces/blog/index.ts
Original file line number Diff line number Diff line change
@@ -36,4 +36,9 @@ export interface BlogCard {
categories: {
name: string
}[]
signedWithGPG: boolean
}

export interface BlogsResponse {
post: BlogCard
}
41 changes: 33 additions & 8 deletions lib/blog/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'server-only'
import { BlogPostRequest, BlogPostResponse } from "@/interfaces/blog"
import { BlogPostRequest, BlogPostResponse, BlogsResponse } from "@/interfaces/blog"
import { Post, PostStatus } from "@prisma/client"
import prisma from "@/lib/prisma"
import redis from "@/lib/redis"
@@ -58,7 +58,7 @@ export async function getPost(id: string): Promise<Post | null> {
return post;
}

export async function getPublishedPosts(): Promise<Post[]> {
export async function getPublishedPosts(): Promise<BlogsResponse[]> {
// Try to get from cache first
const cached = await redis.get(ALL_POSTS_CACHE_KEY);
if (cached) {
@@ -67,9 +67,12 @@ export async function getPublishedPosts(): Promise<Post[]> {

const posts = await prisma.post.findMany({
where: {
status: 'PUBLISHED'
status: PostStatus.PUBLISHED
},
include: {
select: {
id: true,
title: true,
slug: true,
author: {
select: {
name: true
@@ -79,15 +82,29 @@ export async function getPublishedPosts(): Promise<Post[]> {
select: {
name: true
}
}
},
signedWithGPG: true,
createdAt: true
},
orderBy: {
createdAt: 'desc'
}
});

await redis.setex(ALL_POSTS_CACHE_KEY, CACHE_TTL, JSON.stringify(posts));
return posts;

const blogPosts: BlogsResponse[] = posts.map(post => ({
post: {
id: post.id ?? "",
title: post.title ?? "",
slug: post.slug ?? "",
createdAt: post.createdAt.toISOString(),
author: post.author ?? { name: "" },
categories: post.categories ?? [],
signedWithGPG: post.signedWithGPG
}
}));
return blogPosts;
}

export async function getPostPublished(id: string): Promise<Post | null> {
@@ -103,8 +120,16 @@ export async function getPostPublished(id: string): Promise<Post | null> {
const post = await prisma.post.findUnique({
where: { id, status: PostStatus.PUBLISHED },
include: {
categories: true,
author: true
categories: {
select: {
name: true
}
},
author: {
select: {
name: true
}
}
}
});

20 changes: 16 additions & 4 deletions lib/cache/blog/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'client-only'
import { BlogCard } from "@/interfaces/blog"
import { BlogsResponse, BlogCard } from "@/interfaces/blog"
import { Post, User } from "@prisma/client"

const CACHE_PREFIX = 'blog_cache_'
@@ -57,11 +57,23 @@ function getCache<T>(key: string): T | null {

// Blog-specific cache functions
export function setBlogListCache(posts: BlogCard[]): void {
setCache(CACHE_KEYS.BLOG_LIST, posts)
// Transform flat structure to nested before caching
const transformedPosts: BlogsResponse[] = posts.map(post => ({
post: {
id: post.id,
title: post.title,
slug: post.slug,
createdAt: post.createdAt,
author: post.author,
categories: post.categories,
signedWithGPG: post.signedWithGPG
}
}))
setCache(CACHE_KEYS.BLOG_LIST, transformedPosts)
}

export function getBlogListCache(): BlogCard[] | null {
return getCache<BlogCard[]>(CACHE_KEYS.BLOG_LIST)
export function getBlogListCache(): BlogsResponse[] | null {
return getCache<BlogsResponse[]>(CACHE_KEYS.BLOG_LIST)
}

export function setBlogPostCache(slug: string, post: ExtendedPost): void {
18 changes: 17 additions & 1 deletion lib/client/blog/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Abstract for blog API calls
*/
import { BlogPostResponse } from "@/interfaces/blog"
import { BlogPostResponse, BlogsResponse, BlogCard } from "@/interfaces/blog"
import { apiClient } from "@/lib/client"

export async function getPostPublished(id: string): Promise<BlogPostResponse | null> {
@@ -10,3 +10,19 @@ export async function getPostPublished(id: string): Promise<BlogPostResponse | n
})
return response.data as BlogPostResponse
}

export async function getPublishedPosts(): Promise<BlogsResponse[]> {
const response = await apiClient.get('/blog')
// Transform the flat data into the nested structure required by BlogsResponse
return (response.data as BlogCard[]).map(post => ({
post: {
id: post.id,
title: post.title,
slug: post.slug,
createdAt: post.createdAt,
author: post.author,
categories: post.categories,
signedWithGPG: post.signedWithGPG
}
}))
}

0 comments on commit e252609

Please sign in to comment.