Skip to content

Commit

Permalink
task: api GET routes (#17)
Browse files Browse the repository at this point in the history
changes:
- get books (GET /api/books)
- get single book (GET /api/books/[slug])
- get book preview (GET /api/books/[slug]/preview)
- get book chapter (GET /api/books/[slug]/[chapter])
- get featured book (GET /api/books/featured)
additional:
- server types

closes: #17
  • Loading branch information
paul-bokelman committed Jul 25, 2024
1 parent 5c0067e commit 594ea2e
Show file tree
Hide file tree
Showing 15 changed files with 312 additions and 45 deletions.
80 changes: 80 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@prisma/client": "^5.17.0",
"axios": "^1.7.2",
"classnames": "^2.5.1",
"framer-motion": "^11.3.8",
Expand All @@ -25,6 +26,7 @@
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"prisma": "^5.17.0",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
Expand Down
48 changes: 48 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Book {
id Int @id @default(autoincrement())
slug String @unique
title String
author String
description String
cover String
datePublished DateTime
length Int
genre String
accentColor String
chapters Chapter[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Chapter {
id Int @id @default(autoincrement())
number Int
audioUrl String
title String
content String
bookId Int
book Book @relation(fields: [bookId], references: [id])
ambientSections AmbientSection[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model AmbientSection {
id Int @id @default(autoincrement())
start Int
end Int
description String
chapterId Int
chapter Chapter @relation(fields: [chapterId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
1 change: 1 addition & 0 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// seed script
4 changes: 3 additions & 1 deletion src/components/books/book-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export const BookCard: React.FC<Book> = ({ slug, title, cover, author, date, cha
<img src={cover} alt={title} className="rounded-lg w-[100px] group-hover:scale-105 transition-transform" />
<div className="flex flex-col gap-2">
<h2 className="text-base font-bold text-primary font-primary group-hover:text-accent">{title}</h2>
<span className="w-fit text-sm px-1.5 py-0.5 bg-accent/10 text-accent rounded-md">{genre}</span>
<div className="flex justify-center items-center w-fit text-sm px-1.5 py-0.5 bg-accent/10 text-accent rounded-md">
<span className="relative top-0.5">{genre}</span>
</div>
<span className="text-secondary text-sm md:text-base">
{author}{date}
</span>
Expand Down
1 change: 1 addition & 0 deletions src/lib/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./prisma";
13 changes: 13 additions & 0 deletions src/lib/server/prisma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { PrismaClient } from "@prisma/client";

const prismaClientSingleton = () => {
return new PrismaClient();
};

declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;

export const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();

if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;
13 changes: 0 additions & 13 deletions src/pages/api/books/[slug].endpoint.ts

This file was deleted.

28 changes: 28 additions & 0 deletions src/pages/api/books/[slug]/[chapter].endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { AmbientSection, Book, Chapter } from "@prisma/client";
import type { Handler } from "~/types";
import { prisma } from "~/lib/server";

export type GetBookChapterParams = { query: { slug: string; chapter: string } };
export type GetBookChapterPayload = Chapter & {
ambientSections: AmbientSection[];
book: Pick<Book, "title" | "author" | "accentColor">;
};

const handler: Handler<GetBookChapterParams, GetBookChapterPayload> = async (req, res) => {
try {
const chapter = await prisma.chapter.findFirst({
where: { number: parseInt(req.query.chapter), book: { slug: req.query.slug } },
include: { ambientSections: true, book: { select: { title: true, author: true, accentColor: true } } },
});

if (!chapter) {
return res.status(404).json({ status: "error", message: "Chapter not found" });
}

return res.status(200).json({ status: "success", ...chapter });
} catch (e) {
return res.status(500).json({ status: "error", message: "Internal Server Error" });
}
};

export default handler;
25 changes: 25 additions & 0 deletions src/pages/api/books/[slug]/index.endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Book } from "@prisma/client";
import type { Handler } from "~/types";
import { prisma } from "~/lib/server";

export type GetBookParams = { query: { slug: string } };
export type GetBookPayload = Book;

const handler: Handler<GetBookParams, GetBookPayload> = async (req, res) => {
try {
const book = await prisma.book.findUnique({
where: { slug: req.query.slug },
include: { chapters: { include: { ambientSections: true } } },
});

if (!book) {
return res.status(404).json({ status: "error", message: "Book not found" });
}

return res.status(200).json({ status: "success", ...book });
} catch (error) {
return res.status(500).json({ status: "error", message: "Internal server error" });
}
};

export default handler;
22 changes: 22 additions & 0 deletions src/pages/api/books/[slug]/preview.endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Book } from "@prisma/client";
import type { Handler } from "~/types";
import { prisma } from "~/lib/server";

export type GetBookPreviewParams = { query: { slug: string } };
export type GetBookPreviewPayload = Book;

const handler: Handler<GetBookPreviewParams, GetBookPreviewPayload> = async (req, res) => {
try {
const book = await prisma.book.findUnique({ where: { slug: req.query.slug } });

if (!book) {
return res.status(404).json({ status: "error", message: "Book not found" });
}

return res.status(200).json({ status: "success", ...book });
} catch (e) {
return res.status(500).json({ status: "error", message: "Internal Server Error" });
}
};

export default handler;
17 changes: 17 additions & 0 deletions src/pages/api/books/featured.endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Book } from "@prisma/client";
import type { Handler } from "~/types";
import { prisma } from "~/lib/server";

export type GetFeaturedBookParams = {};
export type GetFeaturedBookPayload = Pick<Book, "slug">;

const handler: Handler<GetFeaturedBookParams, GetFeaturedBookPayload> = async (req, res) => {
try {
const latestBook = (await prisma.book.findMany({ orderBy: { id: "desc" }, take: 1, select: { slug: true } }))[0];
return res.status(200).json({ status: "success", slug: latestBook.slug });
} catch (e) {
return res.status(500).json({ status: "error", message: "Internal Server Error" });
}
};

export default handler;
23 changes: 17 additions & 6 deletions src/pages/api/books/index.endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { Books } from "~/types";
import type { Book } from "@prisma/client";
import type { Handler } from "~/types";
import { prisma } from "~/lib/server";

// replace with db
export const books: Books = [
export const books = [
{
slug: "the-great-gatsby",
title: "The Great Gatsby",
Expand Down Expand Up @@ -172,6 +173,16 @@ export const books: Books = [
},
];

export default function handler(req: NextApiRequest, res: NextApiResponse<Books>) {
res.status(200).json(books);
}
export type GetBooksParams = {};
export type GetBooksPayload = { books: Book[] };

const handler: Handler<GetBooksParams, GetBooksPayload> = async (req, res) => {
try {
const books = await prisma.book.findMany();
return res.status(200).json({ status: "success", books });
} catch (e) {
return res.status(500).json({ status: "error", message: "Internal Server Error" });
}
};

export default handler;
15 changes: 15 additions & 0 deletions src/pages/api/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NextRequest } from "next/server";

export const config = {
matcher: "/api/:function*",
};

export const isAuthenticated = (req: NextRequest) => {
return false;
};

export function middleware(request: NextRequest) {
if (!isAuthenticated(request)) {
return Response.json({ success: false, message: "authentication failed" }, { status: 401 });
}
}
Loading

0 comments on commit 594ea2e

Please sign in to comment.