From dd121030e4757f5f2ad045b279fc69efcd26b629 Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 7 Jan 2023 08:52:59 +0100 Subject: [PATCH] add api --- .prettierrc | 5 + app/Nav.tsx | 235 +- .../[articleName]/ContentTable.tsx | 47 +- .../[categoryName]/[articleName]/page.tsx | 133 +- app/articles/[categoryName]/page.tsx | 133 +- app/articles/page.tsx | 72 +- app/global.ts | 3 + app/layout.tsx | 46 +- next.config.js | 13 +- package-lock.json | 15044 +++++++--------- package.json | 78 +- pages/api/articles/[articleName].ts | 31 + pages/api/articles/index.ts | 53 + pages/api/categories/[categoryName].tsx | 31 + pages/api/categories/index.tsx | 29 + pages/api/search.tsx | 44 +- .../20230107035654_init/migration.sql | 109 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 7 + public/images/blur.png | Bin 0 -> 549 bytes public/images/docker.png | Bin 0 -> 44975 bytes public/images/test.jpg | Bin 0 -> 2341241 bytes styles/modules/Article.module.scss | 37 + .../modules/ArticleContentTable.module.scss | 29 + styles/modules/Nav.module.scss | 1 + styles/modules/Tutorial.module.scss | 21 - .../modules/TutorialContentTable.module.scss | 29 - styles/typography.scss | 80 +- styles/variables_colors.scss | 93 +- types/responseErrors.tsx | 4 + 30 files changed, 7745 insertions(+), 8665 deletions(-) create mode 100644 .prettierrc create mode 100644 app/global.ts create mode 100644 pages/api/articles/[articleName].ts create mode 100644 pages/api/articles/index.ts create mode 100644 pages/api/categories/[categoryName].tsx create mode 100644 pages/api/categories/index.tsx create mode 100644 prisma/migrations/20230107035654_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 public/images/blur.png create mode 100644 public/images/docker.png create mode 100644 public/images/test.jpg create mode 100644 styles/modules/Article.module.scss create mode 100644 styles/modules/ArticleContentTable.module.scss delete mode 100644 styles/modules/Tutorial.module.scss delete mode 100644 styles/modules/TutorialContentTable.module.scss create mode 100644 types/responseErrors.tsx diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..054d599 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": false, + "printWidth": 120 +} diff --git a/app/Nav.tsx b/app/Nav.tsx index adea363..59452af 100644 --- a/app/Nav.tsx +++ b/app/Nav.tsx @@ -2,146 +2,127 @@ import styles from "../styles/modules/Nav.module.scss"; import Image from "next/image"; import Link from "next/link"; -import { MutableRefObject, useEffect, useRef, useState } from "react"; -import async from "./articles/[categoryName]/[articleName]/head"; -import ContentTable from "./articles/[categoryName]/[articleName]/ContentTable"; - -export type NavCategory = { - name: string; - title: string; -}; +import { useEffect, useState } from "react"; +import { Category } from "@prisma/client"; +import urlJoin from "url-join"; +import { apiUrl } from "./global"; +import { GetStaticProps } from "next"; function switchTheme(theme) { - const bodyElement = document.getElementsByTagName("body")[0]; + const bodyElement = document.getElementsByTagName("body")[0]; - if (theme == "dark") { - bodyElement.classList.remove("theme-light"); - } else { - bodyElement.classList.add("theme-light"); - } + if (theme == "dark") { + bodyElement.classList.remove("theme-light"); + } else { + bodyElement.classList.add("theme-light"); + } } function toggleTheme() { - const svgElement = document.getElementById("themeSwitchSvg"); + const svgElement = document.getElementById("themeSwitchSvg"); - if (localStorage.getItem("theme") == "light") { - svgElement.style.animationDirection = "normal"; - svgElement.style.animationName = styles.spinThemeSwitch; - } else { - svgElement.style.animationDirection = "reverse"; - svgElement.style.animationName = styles.spinThemeSwitch; - } + if (localStorage.getItem("theme") == "light") { + svgElement.style.animationDirection = "normal"; + svgElement.style.animationName = styles.spinThemeSwitch; + } else { + svgElement.style.animationDirection = "reverse"; + svgElement.style.animationName = styles.spinThemeSwitch; + } - setTimeout(() => { - if (localStorage.getItem("theme") == "light") { - localStorage.setItem("theme", "dark"); - switchTheme("dark"); - } else { - localStorage.setItem("theme", "light"); - switchTheme("light"); - } - svgElement.style.animationName = ""; - }, 150); + setTimeout(() => { + if (localStorage.getItem("theme") == "light") { + localStorage.setItem("theme", "dark"); + switchTheme("dark"); + } else { + localStorage.setItem("theme", "light"); + switchTheme("light"); + } + svgElement.style.animationName = ""; + }, 150); } -export default function Nav({ categories }: { categories: NavCategory[] }) { - const [searchResults, setSearchResults] = useState([]); +export default function Nav({ categories }: { categories: Category[] }) { + const [searchResults, setSearchResults] = useState([]); - async function handleSearchInput(event) { - const query = event.target.value; - let result = await fetch(`/api/search?q=${query}`); - let json = await result.json(); + async function handleSearchInput(event) { + const query = event.target.value; + let result = await fetch(`/api/search?q=${query}`); + let json = await result.json(); - if (json.length == 0 && query.length > 0) { - setSearchResults([{ name: "", title: "No article found..." }]); - } else { - setSearchResults(json); - } - } + if (json.length == 0 && query.length > 0) { + setSearchResults([{ name: "", title: "No article found..." }]); + } else { + setSearchResults(json); + } + } - useEffect(() => { - if (localStorage.getItem("theme") == "dark") { - switchTheme("dark"); - } else { - switchTheme("light"); - } - }, []); + useEffect(() => { + if (localStorage.getItem("theme") == "dark") { + switchTheme("dark"); + } else { + switchTheme("light"); + } + }, []); - useEffect(() => { - console.log(searchResults); - }, [searchResults]); + useEffect(() => { + console.log(searchResults); + }, [searchResults]); - return ( - - ); + return ( + + ); } diff --git a/app/articles/[categoryName]/[articleName]/ContentTable.tsx b/app/articles/[categoryName]/[articleName]/ContentTable.tsx index 9b8db5f..2c55083 100644 --- a/app/articles/[categoryName]/[articleName]/ContentTable.tsx +++ b/app/articles/[categoryName]/[articleName]/ContentTable.tsx @@ -1,32 +1,23 @@ import React from "react"; -import prisma from "../../../../lib/prisma"; -import styles from "../../../../styles/modules/TutorialContentTable.module.scss"; +import styles from "../../../../styles/modules/ArticleContentTable.module.scss"; import { Article, ContentTableEntry } from "@prisma/client"; -export default function ContentTable({ - contentTableEntries, -}: { - contentTableEntries: ContentTableEntry[]; -}) { - return ( -
-
-
-

Contents

- {contentTableEntries?.map((e, i) => { - return ( - - {e.title} - - ); - })} -
- {contentTableEntries?.length < 15 ? ( -
Future advertisement
- ) : ( - "" - )} -
-
- ); +export default function ContentTable({ contentTableEntries }: { contentTableEntries: ContentTableEntry[] }) { + return ( +
+
+
+

Contents

+ {contentTableEntries?.map((e, i) => { + return ( + + {e.title} + + ); + })} +
+ {contentTableEntries?.length < 15 ?
Future advertisement
: ""} +
+
+ ); } diff --git a/app/articles/[categoryName]/[articleName]/page.tsx b/app/articles/[categoryName]/[articleName]/page.tsx index 094efb9..81c4163 100644 --- a/app/articles/[categoryName]/[articleName]/page.tsx +++ b/app/articles/[categoryName]/[articleName]/page.tsx @@ -1,82 +1,93 @@ import { marked } from "marked"; import ContentTable from "./ContentTable"; import Sidebar from "./Sidebar"; -import styles from "../../../../styles/modules/Tutorial.module.scss"; +import styles from "../../../../styles/modules/Article.module.scss"; import LoadMarkdown from "./LoadMarkdown"; -import prisma from "../../../../lib/prisma"; import { Article, Category, ContentTableEntry } from "@prisma/client"; +import Image from "next/image"; +import urlJoin from "url-join"; +import { apiUrl } from "../../../global"; +import { Prisma } from "@prisma/client"; -export async function GetContentTableEntries( - article: Article -): Promise { - const entries = await prisma.contentTableEntry.findMany({ - where: { articleId: article?.id ?? 1 }, - orderBy: { orderIndex: "asc" }, - }); +type ArticleWithContentTableEntries = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true } }>; +type ArticleWithCategory = Prisma.ArticleGetPayload<{ include: { category: true } }>; - return entries; -} +// export async function GetContentTableEntries(article: Article): Promise { +// const entries = await prisma.contentTableEntry.findMany({ +// where: { articleId: article?.id ?? 1 }, +// orderBy: { orderIndex: "asc" }, +// }); -export async function GetArticle(articleName: string) { - const article = await prisma.article.findUnique({ - where: { name: articleName.toLowerCase() ?? "" }, - }); +// return entries; +// } - return article; +export async function GetArticle(articleName: string): Promise { + const result: Response = await fetch(urlJoin(apiUrl, `articles/${articleName ?? ""}`), { + cache: "force-cache", + next: { revalidate: 60 * 10 }, + }); + + return result.json(); } function ParseMarkdown(markdown: string): string { - let result = marked.parse(markdown); - - return result; + let result = marked.parse(markdown); + return result; } //* MAIN -export default async function ArticlePage({ - params, -}: { - params: { articleName: string; categoryName: string }; -}) { - const articleName: string = params.articleName - .toLowerCase() - .replaceAll("%20", " "); - const article: Article = await GetArticle(articleName); - const markdown: string = article?.markdown ?? ""; - const contentTableEntries: ContentTableEntry[] = await GetContentTableEntries( - article - ); +export default async function ArticlePage({ params }: { params: { articleName: string; categoryName: string } }) { + const articleName: string = params.articleName.toLowerCase().replaceAll("%20", " "); + const article: ArticleWithContentTableEntries = await GetArticle(articleName); + const markdown: string = article?.markdown ?? ""; - return ( -
- -
-
-

{article?.title}

-
-
- -
- -
- ); + return ( +
+ +
+
+

Published on January 13, 2022

+ +

{article?.title}

+ + Image +
+
+ +
+ +
+ ); } export async function generateStaticParams() { - const articles = await prisma.article.findMany(); + const articles: ArticleWithCategory[] = await ( + await fetch(urlJoin(apiUrl, `articles/`), { + cache: "force-cache", + next: { revalidate: 60 * 10 }, + }) + ).json(); - async function GetCategory(categoryId: number): Promise { - return await prisma.category.findUnique({ where: { id: categoryId } }); - } - - return await Promise.all( - articles.map(async (article) => ({ - categoryName: (await GetCategory(article.categoryId)).name ?? "", - articleName: article.name ?? "", - })) - ); + return await Promise.all( + articles.map(async (article) => ({ + categoryName: article.category?.name ?? "", + articleName: article.name ?? "", + })) + ); } diff --git a/app/articles/[categoryName]/page.tsx b/app/articles/[categoryName]/page.tsx index 1a26f8b..8ec97ad 100644 --- a/app/articles/[categoryName]/page.tsx +++ b/app/articles/[categoryName]/page.tsx @@ -1,63 +1,72 @@ import styles from "../../../styles/modules/Category.module.scss"; import Link from "next/link"; -import prisma from "../../../lib/prisma"; +import { apiUrl } from "../../global"; import { Article, Category } from "@prisma/client"; +import urlJoin from "url-join"; -export async function GetAllArticles(category: Category): Promise { - return await prisma.article.findMany({ where: { category: category } }); +async function GetAllArticles(categoryName: string): Promise { + const result: Response = await fetch(urlJoin(apiUrl, `articles?categoryName=${categoryName}`), { + cache: "force-cache", + next: { revalidate: 3600 }, + }); + return result.json(); } -export async function GetPopularArticles( - category: Category -): Promise { - return await prisma.article.findMany({ - where: { category: category }, - take: 6, - }); +async function GetPopularArticles(categoryName: string): Promise { + const result: Response = await fetch( + urlJoin(apiUrl, `articles?categoryName=${categoryName}&limit=6&orderBy=popularity`), + { + cache: "force-cache", + next: { revalidate: 3600 }, + } + ); + return result.json(); } -export async function GetRecentArticles( - category: Category -): Promise { - return await prisma.article.findMany({ - where: { category: category }, - take: 6, - orderBy: { dateCreated: "desc" }, - }); +async function GetRecentArticles(categoryName: string): Promise { + const result: Response = await fetch( + urlJoin(apiUrl, `articles?categoryName=${categoryName}&limit=6&orderBy=recent`), + { + cache: "force-cache", + next: { revalidate: 3600 }, + } + ); + return result.json(); } -export async function GetCategory(categoryName: string): Promise { - return await prisma.category.findUnique({ where: { name: categoryName } }); +async function GetCategory(categoryName: string): Promise { + const result: Response = await fetch(urlJoin(apiUrl, `categories/${categoryName}`), { + cache: "force-cache", + next: { revalidate: 3600 }, + }); + return result.json(); } -export default async function CategoryPage({ - params, -}: { - params: { categoryName: string }; -}) { - const categoryName = params.categoryName.toLowerCase().replaceAll("%20", " "); - const category: Category = await GetCategory(categoryName); - const allArticles: Article[] = await GetAllArticles(category); - const popularArticles: Article[] = await GetPopularArticles(category); - const recentArticles: Article[] = await GetRecentArticles(category); - return ( -
-

{category?.title}

-
-
-

Most popular articles

- {popularArticles?.map((a, i) => { - { - return ( - - {a.name} - - ); - } - })} -
+export default async function CategoryPage({ params }: { params: { categoryName: string } }) { + const categoryName = params.categoryName.toLowerCase().replaceAll("%20", " "); + const category: Category = await GetCategory(categoryName); + const allArticles: Article[] = await GetAllArticles(categoryName); + const popularArticles: Article[] = await GetPopularArticles(categoryName); + const recentArticles: Article[] = await GetRecentArticles(categoryName); + console.log(popularArticles); + return ( +
+

{category?.title}

+
+
+

Most popular articles

+ {popularArticles?.map((a, i) => { + { + return ( + + {a.name} + + ); + } + })} +
- {/*
+ {/*

Most recent articles

{recentArticles?.map((a, i) => { { @@ -70,19 +79,19 @@ export default async function CategoryPage({ })}
*/} -
-

All articles

- {allArticles?.map((a, i) => { - { - return ( - - {a.name} - - ); - } - })} -
-
-
- ); +
+

All articles

+ {allArticles?.map((a, i) => { + { + return ( + + {a.name} + + ); + } + })} +
+
+
+ ); } diff --git a/app/articles/page.tsx b/app/articles/page.tsx index 1bd6654..9aaf721 100644 --- a/app/articles/page.tsx +++ b/app/articles/page.tsx @@ -1,47 +1,45 @@ import styles from "../../styles/modules/CategoryList.module.scss"; import Link from "next/link"; -import prisma from "../../lib/prisma"; import { Category, Svg, Prisma } from "@prisma/client"; -import { Suspense } from "react"; -import dynamic from "next/dynamic"; +import urlJoin from "url-join"; +import { apiUrl } from "../global"; type CategoryWithSvg = Prisma.CategoryGetPayload<{ include: { svg: true } }>; -export async function GetCategories(): Promise { - return await prisma.category.findMany({ include: { svg: true } }); +export async function GetCategories(): Promise { + const result: Response = await fetch(urlJoin(apiUrl, `categories`), { + cache: "force-cache", + next: { revalidate: 3600 }, + }); + + return result.json(); } export default async function CategoryList() { - const categories = await GetCategories(); - return ( -
-

Overview

-
-
- {categories?.length > 0 - ? categories.map((cat, i) => { - return ( -
- -
- - - -
- {cat.title} - -
- ); - }) - : "We did not find any categories"} -
-
-
- ); + const categories = await GetCategories(); + return ( +
+

Overview

+
+
+ {categories?.length > 0 + ? categories.map((cat, i) => { + return ( +
+ +
+ + + +
+ {cat.title} + +
+ ); + }) + : "We did not find any categories"} +
+
+
+ ); } diff --git a/app/global.ts b/app/global.ts new file mode 100644 index 0000000..5567214 --- /dev/null +++ b/app/global.ts @@ -0,0 +1,3 @@ +//! Using this because publicRuntimeConfig is not implemented in appDir yet + +export const apiUrl: string = "http://localhost:3000/api/"; \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index edea00e..8a02935 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,32 +4,30 @@ import "../styles/variables.scss"; import Nav from "./Nav"; import Footer from "./Footer"; import { Category } from "@prisma/client"; -import prisma from "../lib/prisma"; -import { NavCategory } from "./Nav"; +import urlJoin from "url-join"; +import { apiUrl } from "./global"; -export async function GetNavCategories(): Promise { - const result: NavCategory[] = await prisma.category.findMany({ - select: { name: true, title: true }, - }); - return result; +async function getCategories(): Promise { + const result: Response = await fetch(urlJoin(apiUrl, `categories`), { + cache: "force-cache", + next: { revalidate: 3600 }, + }); + + return await result.json(); } -export default async function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - +export default async function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + - -
-
-
{children}
-