Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: posts schema structure and better partial rendering #140

Merged
merged 3 commits into from
Oct 9, 2024
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
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