Skip to content

Commit

Permalink
refactor: posts schema structure and better partial rendering (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
moonlitgrace authored Oct 9, 2024
1 parent ee5d20e commit 22c1b35
Show file tree
Hide file tree
Showing 15 changed files with 85 additions and 51 deletions.
2 changes: 1 addition & 1 deletion __tests__/unit-tests/extract-pagragraphs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extractParagraphs } from '@/lib/utils';
import { extractParagraphs } from '@/app/_lib/utils';

describe('extract paragraphs only from markdown', () => {
it('should return paragraph', () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/unit-tests/truncate.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { truncate } from '@/lib/utils';
import { truncate } from '@/app/_lib/utils';

describe('truncate char', () => {
const exampleStr = 'Step into Moonlitgrace';
Expand Down
30 changes: 14 additions & 16 deletions app/(routes)/(main)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PostSelect } from '@/db/schema';
import { extractParagraphs, formatDate, truncate } from '@/lib/utils';
import { formatDate } from '@/lib/utils';
import { marked, Tokens } from 'marked';
import { Metadata } from 'next';
import Image from 'next/image';
Expand Down Expand Up @@ -29,10 +29,10 @@ export async function generateMetadata({
},
};

const post = await res.json().then((res) => res.data);

const { title, content, cover, slug, tag, createdAt } = post;
const description = truncate(extractParagraphs(content), 160);
const post: Omit<PostSelect, 'id' | 'draft' | 'content'> = await res
.json()
.then((res) => res.data);
const { title, description, cover, slug, tag, createdAt } = post;

// og: dynamic image
const ogImgUrl = new URL(process.env.NEXT_PUBLIC_APP_URL + '/api/og');
Expand Down Expand Up @@ -63,17 +63,15 @@ export async function generateMetadata({
}

export default async function Page({ params }: { params: { slug: string } }) {
const postData: PostSelect = await fetch(
`${process.env.NEXT_PUBLIC_APP_URL}/api/blog/${params.slug}`,
)
const post: PostSelect = await fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/blog/${params.slug}`)
.then((res) => {
if (res.status === 404) notFound();
return res.json();
})
.then((res) => res.data);

const lexer = new marked.Lexer();
const tokens = lexer.lex(postData.content);
const tokens = lexer.lex(post.content);
const headings = tokens
.filter((token) => token.type === 'heading')
.map((token) => (token as Tokens.Heading).text);
Expand All @@ -82,14 +80,14 @@ export default async function Page({ params }: { params: { slug: string } }) {
<>
<div className="flex w-full flex-col items-center gap-5">
<h4 className="text-xs font-bold uppercase text-muted-foreground">
{formatDate(postData.createdAt)}
{formatDate(post.createdAt)}
</h4>
<h1 className="text-center text-4xl font-black leading-snug">{postData.title}</h1>
<Badge className="capitalize">{postData.tag}</Badge>
{postData.cover && (
<h1 className="text-center text-4xl font-black leading-snug">{post.title}</h1>
<Badge className="capitalize">{post.tag}</Badge>
{post.cover && (
<Image
src={postData.cover}
alt={postData.title}
src={post.cover}
alt={post.title}
priority={true}
width={0}
height={0}
Expand All @@ -99,7 +97,7 @@ export default async function Page({ params }: { params: { slug: string } }) {
/>
)}
</div>
<Markdown markdown={postData.content} />
<Markdown markdown={post.content} />
{headings.length > 0 && <TableOfContents headings={headings} />}
</>
);
Expand Down
6 changes: 2 additions & 4 deletions app/(routes)/(main)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Badge } from '@/components/ui/badge';
import { PostSelect } from '@/db/schema';
import { cn, extractParagraphs, formatDate, truncate } from '@/lib/utils';
import { cn, formatDate } from '@/lib/utils';
import Link from 'next/link';
import { Metadata } from 'next';
import Image from 'next/image';
Expand Down Expand Up @@ -64,9 +64,7 @@ export default async function BlogPage() {
<Link href={`/blog/${post.slug}`} className="relative text-xl font-bold underline">
{post.title}
</Link>
<p className="text-sm text-muted-foreground">
{truncate(extractParagraphs(post.content), 100)}
</p>
<p className="text-sm text-muted-foreground">{post.description}</p>
</div>
</div>
))}
Expand Down
17 changes: 17 additions & 0 deletions app/_lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { stripHtmlTags } from '@/lib/utils';
import { marked, Tokens } from 'marked';

export function extractParagraphs(markdown: string) {
const tokens = marked.lexer(markdown);
const paragraphs = tokens
.filter((token) => token.type === 'paragraph')
.map((token) => {
const rawText = marked.parseInline((token as Tokens.Paragraph).text, { async: false });
return stripHtmlTags(rawText);
});
return paragraphs.join(' ');
}

export function truncate(str: string, n: number) {
return str.length > n ? str.slice(0, n - 3) + '...' : str;
}
20 changes: 17 additions & 3 deletions app/api/blog/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { db } from '@/db';
import { posts, PostSelect } from '@/db/schema';
import { extractParagraphs, truncate } from '@/app/_lib/utils';
import { AdminBlogData } from '@/zod_schemas/admin';
import { desc, eq } from 'drizzle-orm';
import { NextRequest, NextResponse } from 'next/server';
import slugify from 'slugify';

export async function GET(_req: NextRequest) {
console.log('API called');
export async function GET(_request: NextRequest) {
try {
const postsData: PostSelect[] = await db.select().from(posts).orderBy(desc(posts.createdAt));
const postsData: Omit<PostSelect, 'content'>[] = await db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
tag: posts.tag,
cover: posts.cover,
description: posts.description,
createdAt: posts.createdAt,
draft: posts.draft,
})
.from(posts)
.orderBy(desc(posts.createdAt));

return NextResponse.json({ data: postsData, message: 'success' });
} catch (err) {
Expand All @@ -29,6 +41,7 @@ export async function POST(request: NextRequest) {
title: data.title,
tag: data.tag,
content: data.content,
description: truncate(extractParagraphs(data.content), 160),
slug: slugify(data.title.toLowerCase()),
...(data.cover && { cover: data.cover }),
draft: data.draft,
Expand All @@ -41,6 +54,7 @@ export async function POST(request: NextRequest) {
title: data.title,
tag: data.tag,
content: data.content,
description: truncate(extractParagraphs(data.content), 160),
slug: slugify(data.title.toLowerCase()),
...(data.cover && { cover: data.cover }),
draft: data.draft,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ CREATE TABLE IF NOT EXISTS "posts" (
"tag" text NOT NULL,
"cover" text,
"content" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
"description" text DEFAULT 'default:' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"draft" boolean DEFAULT false NOT NULL
);
1 change: 0 additions & 1 deletion db/migrations/0001_slow_scarlet_spider.sql

This file was deleted.

1 change: 1 addition & 0 deletions db/migrations/0001_stormy_energizer.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "posts" ALTER COLUMN "description" DROP DEFAULT;
16 changes: 15 additions & 1 deletion db/migrations/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "a0e38a8e-5c92-4669-9f13-789788513011",
"id": "04cf5614-0ac7-4007-8d19-a50f67424b29",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
Expand Down Expand Up @@ -44,12 +44,26 @@
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'default:'"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"indexes": {},
Expand Down
10 changes: 8 additions & 2 deletions db/migrations/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "bfb47a84-8aa3-4498-a3c3-d57b380d926b",
"prevId": "a0e38a8e-5c92-4669-9f13-789788513011",
"id": "013ff335-d84d-4aa4-bcd6-5da5816bc397",
"prevId": "04cf5614-0ac7-4007-8d19-a50f67424b29",
"version": "7",
"dialect": "postgresql",
"tables": {
Expand Down Expand Up @@ -44,6 +44,12 @@
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
Expand Down
8 changes: 4 additions & 4 deletions db/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
{
"idx": 0,
"version": "7",
"when": 1725340620008,
"tag": "0000_shocking_absorbing_man",
"when": 1728442840815,
"tag": "0000_silent_smasher",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1726658878773,
"tag": "0001_slow_scarlet_spider",
"when": 1728443335073,
"tag": "0001_stormy_energizer",
"breakpoints": true
}
]
Expand Down
1 change: 1 addition & 0 deletions db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const posts = pgTable('posts', {
tag: text('tag').notNull(),
cover: text('cover'),
content: text('content').notNull(),
description: text('description').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
draft: boolean('draft').notNull().default(false),
});
Expand Down
17 changes: 0 additions & 17 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { marked, Tokens } from 'marked';

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
Expand All @@ -27,22 +26,6 @@ export function stripHtmlTags(html: string) {
return html.replace(/<[^>]*>/g, '');
}

export function extractParagraphs(markdown: string) {
const tokens = marked.lexer(markdown);
const paragraphs = tokens
.filter((token) => token.type === 'paragraph')
.map((token) => {
const rawText = marked.parseInline((token as Tokens.Paragraph).text, { async: false });
return stripHtmlTags(rawText);
});
return paragraphs.join(' ');
}

export function truncate(str: string, n: number) {
return str.length > n ? str.slice(0, n - 3) + '...' : str;
}

// TODO: add test for this function
export function arrayBufferToBase64(buffer: ArrayBuffer): string {
let binary = '';
const bytes = new Uint8Array(buffer);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"db:generate": "drizzle-kit generate --config=./drizzle.config.ts",
"db:migrate": "drizzle-kit migrate --config=./drizzle.config.ts",
"db:migrate:prod": "cross-env NODE_ENV=production dotenvx run -f .env.production.local -- drizzle-kit migrate --config=./drizzle.config.ts",
"db:push": "drizzle-kit push --config=./drizzle.config.ts",
"db:studio": "drizzle-kit studio --config=./drizzle.config.ts",
"prepare": "husky"
},
Expand Down

0 comments on commit 22c1b35

Please sign in to comment.