diff --git a/package-lock.json b/package-lock.json index 13f9c06..038ca36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,8 @@ "react-select": "^5.7.3", "sass": "^1.62.1", "sharp": "^0.32.1", - "toastr": "^2.1.4" + "toastr": "^2.1.4", + "yup": "^1.2.0" }, "devDependencies": { "@types/node": "^20.2.4", @@ -6005,6 +6006,11 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -6811,6 +6817,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", @@ -6848,6 +6859,11 @@ "jquery": ">=1.12.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -7161,6 +7177,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz", + "integrity": "sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.21.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", diff --git a/package.json b/package.json index 76eeea0..82cb217 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "react-select": "^5.7.3", "sass": "^1.62.1", "sharp": "^0.32.1", - "toastr": "^2.1.4" + "toastr": "^2.1.4", + "yup": "^1.2.0" }, "devDependencies": { "@types/node": "^20.2.4", diff --git a/src/lib/category/getUserCategories.ts b/src/lib/category/getUserCategories.ts new file mode 100644 index 0000000..81bf447 --- /dev/null +++ b/src/lib/category/getUserCategories.ts @@ -0,0 +1,17 @@ +import { User } from "@prisma/client"; +import prisma from "utils/prisma"; + +export default async function getUserCategories(user: User) { + return await prisma.category.findMany({ + where: { + authorId: user.id, + }, + include: { + links: { + where: { + authorId: user.id, + }, + }, + }, + }); +} diff --git a/src/lib/link/getUserLink.ts b/src/lib/link/getUserLink.ts new file mode 100644 index 0000000..54bfe65 --- /dev/null +++ b/src/lib/link/getUserLink.ts @@ -0,0 +1,14 @@ +import { Link, User } from "@prisma/client"; +import prisma from "utils/prisma"; + +export default async function getUserLink(user: User, id: Link["id"]) { + return await prisma.link.findFirst({ + where: { + id, + authorId: user.id, + }, + include: { + category: true, + }, + }); +} diff --git a/src/lib/link/getUserLinks.ts b/src/lib/link/getUserLinks.ts new file mode 100644 index 0000000..7d8de9a --- /dev/null +++ b/src/lib/link/getUserLinks.ts @@ -0,0 +1,10 @@ +import { User } from "@prisma/client"; +import prisma from "utils/prisma"; + +export default async function getUserLinks(user: User) { + return await prisma.link.findMany({ + where: { + authorId: user.id, + }, + }); +} diff --git a/src/lib/user/getUserOrThrow.ts b/src/lib/user/getUserOrThrow.ts new file mode 100644 index 0000000..5ba5b31 --- /dev/null +++ b/src/lib/user/getUserOrThrow.ts @@ -0,0 +1,10 @@ +import { Session } from "next-auth"; +import prisma from "utils/prisma"; + +export default async function getUserOrThrow(session: Session) { + return await prisma.user.findFirstOrThrow({ + where: { + email: session?.user?.email, + }, + }); +} diff --git a/src/pages/api/auth/[...nextauth].ts b/src/pages/api/auth/[...nextauth].ts index f0aab3a..80becdd 100644 --- a/src/pages/api/auth/[...nextauth].ts +++ b/src/pages/api/auth/[...nextauth].ts @@ -33,6 +33,7 @@ export const authOptions = { "[AUTH]", "User", profile.name, + profile.sub, "attempt to log in with", accountParam.provider ); diff --git a/src/pages/api/category/create.ts b/src/pages/api/category/create.ts index ccd63f6..054e102 100644 --- a/src/pages/api/category/create.ts +++ b/src/pages/api/category/create.ts @@ -1,64 +1,40 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../auth/[...nextauth]"; +import { checkMethodAllowedOrThrow } from "utils/api"; import prisma from "utils/prisma"; +import { getSessionOrThrow } from "utils/session"; + +export default async function POST(req: NextApiRequest, res: NextApiResponse) { + await checkMethodAllowedOrThrow(req, ["post"]); + const session = await getSessionOrThrow(req, res); -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const session = await getServerSession(req, res, authOptions); // je sais plus d'où ça vient - console.log("session", session); const name = req.body?.name as string; - if (!name) { - return res.status(400).send({ error: "Nom de catégorie manquant" }); + throw new Error("Categorie name missing"); } - try { - const category = await prisma.category.findFirst({ - where: { name }, - }); - - if (category) { - return res - .status(400) - .send({ error: "Une catégorie avec ce nom existe déjà" }); - } - } catch (error) { - console.error(error); - return res.status(400).send({ - error: - "Une erreur est survenue lors de la création de la catégorie (category/create->findCategory)", - }); + const category = await prisma.category.findFirst({ + where: { name }, + }); + if (category) { + throw new Error("Category name already used"); } - try { - const { id: authorId } = await prisma.user.findFirst({ - where: { - email: session.user.email, - }, - select: { - id: true, - }, - }); - if (!authorId) { - throw new Error("Unable to find user"); - } + const { id: authorId } = await prisma.user.findFirstOrThrow({ + where: { + email: session.user.email, + }, + select: { + id: true, + }, + }); - const category = await prisma.category.create({ - data: { name, authorId }, - }); - return res.status(200).send({ - success: "Catégorie créée avec succès", - categoryId: category.id, - }); - } catch (error) { - console.error(error); - return res.status(400).send({ - error: - "Une erreur est survenue lors de la création de la catégorie (category/create->createCategory)", - }); - } + const categoryCreated = await prisma.category.create({ + data: { name, authorId }, + }); + console.log("là"); + return res.status(200).send({ + success: "Category successfully created", + categoryId: categoryCreated.id, + }); } diff --git a/src/pages/api/link/create.ts b/src/pages/api/link/create.ts index eca3f39..02ed944 100644 --- a/src/pages/api/link/create.ts +++ b/src/pages/api/link/create.ts @@ -1,10 +1,19 @@ import { NextApiRequest, NextApiResponse } from "next"; +import { getServerSession } from "next-auth/next"; + import prisma from "utils/prisma"; +import { authOptions } from "../auth/[...nextauth]"; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { + const session = await getServerSession(req, res, authOptions); // je sais plus d'où ça vient + console.log("session", session); + if (!session?.user) { + return res.status(400).send({ error: "You must be connected" }); + } + const name = req.body?.name as string; const url = req.body?.url as string; const favorite = Boolean(req.body?.favorite) || false; @@ -57,12 +66,16 @@ export default async function handler( } try { + const { id: authorId } = await prisma.user.findFirstOrThrow({ + where: { email: session.user.email }, + }); await prisma.link.create({ data: { name, url, categoryId, favorite, + authorId, }, }); return res diff --git a/src/pages/category/edit/[cid].tsx b/src/pages/category/edit/[cid].tsx index 69f6be9..455bec7 100644 --- a/src/pages/category/edit/[cid].tsx +++ b/src/pages/category/edit/[cid].tsx @@ -79,7 +79,7 @@ export async function getServerSideProps({ query }) { const { cid } = query; const categoryDB = await prisma.category.findFirst({ where: { id: Number(cid) }, - include: { links: true }, + include: { links: true, author: true }, }); if (!categoryDB) { diff --git a/src/pages/category/remove/[cid].tsx b/src/pages/category/remove/[cid].tsx index 0974deb..f4ef4e8 100644 --- a/src/pages/category/remove/[cid].tsx +++ b/src/pages/category/remove/[cid].tsx @@ -84,7 +84,7 @@ export async function getServerSideProps({ query }) { const { cid } = query; const categoryDB = await prisma.category.findFirst({ where: { id: Number(cid) }, - include: { links: true }, + include: { links: true, author: true }, }); if (!categoryDB) { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 9742e5b..6ec8d81 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -16,6 +16,7 @@ import { Category, Link, SearchItem } from "types"; import { BuildCategory } from "utils/front"; import { pushStateVanilla } from "utils/link"; import prisma from "utils/prisma"; +import { getSessionOrThrow } from "utils/session"; interface HomePageProps { categories: Category[]; @@ -185,10 +186,27 @@ function Home(props: HomePageProps) { ); } -export async function getServerSideProps({ query }) { +export async function getServerSideProps({ req, res, query }) { + const session = await getSessionOrThrow(req, res); const queryCategoryId = (query?.categoryId as string) || ""; + + const user = await prisma.user.findFirstOrThrow({ + where: { + email: session.user.email, + }, + }); const categoriesDB = await prisma.category.findMany({ - include: { links: true }, + include: { + links: { + where: { + authorId: user.id, + }, + }, + author: true, + }, + where: { + authorId: user.id, + }, }); if (categoriesDB.length === 0) { diff --git a/src/pages/link/create.tsx b/src/pages/link/create.tsx index 51a7daf..7694ee2 100644 --- a/src/pages/link/create.tsx +++ b/src/pages/link/create.tsx @@ -111,7 +111,9 @@ CreateLink.authRequired = true; export default CreateLink; export async function getServerSideProps() { - const categoriesDB = await prisma.category.findMany(); + const categoriesDB = await prisma.category.findMany({ + include: { author: true }, + }); const categories = categoriesDB.map((categoryDB) => BuildCategory(categoryDB) ); diff --git a/src/pages/link/edit/[lid].tsx b/src/pages/link/edit/[lid].tsx index 37c5bd4..678dab3 100644 --- a/src/pages/link/edit/[lid].tsx +++ b/src/pages/link/edit/[lid].tsx @@ -11,15 +11,13 @@ import TextBox from "components/TextBox"; import useAutoFocus from "hooks/useAutoFocus"; import { Category, Link } from "types"; -import { - BuildCategory, - BuildLink, - HandleAxiosError, - IsValidURL, -} from "utils/front"; -import prisma from "utils/prisma"; +import { HandleAxiosError, IsValidURL } from "utils/front"; +import getUserCategories from "lib/category/getUserCategories"; +import getUserLink from "lib/link/getUserLink"; +import getUserOrThrow from "lib/user/getUserOrThrow"; import styles from "styles/create.module.scss"; +import { getSessionOrThrow } from "utils/session"; function EditLink({ link, @@ -133,20 +131,15 @@ function EditLink({ EditLink.authRequired = true; export default EditLink; -export async function getServerSideProps({ query }) { +export async function getServerSideProps({ req, res, query }) { const { lid } = query; - const categoriesDB = await prisma.category.findMany(); - const categories = categoriesDB.map((categoryDB) => - BuildCategory(categoryDB) - ); + const session = await getSessionOrThrow(req, res); + const user = await getUserOrThrow(session); + const categories = await getUserCategories(user); - const linkDB = await prisma.link.findFirst({ - where: { id: Number(lid) }, - include: { category: true }, - }); - - if (!linkDB) { + const link = await getUserLink(user, Number(lid)); + if (!link) { return { redirect: { destination: "/", @@ -154,10 +147,6 @@ export async function getServerSideProps({ query }) { }; } - const link = BuildLink(linkDB, { - categoryId: linkDB.categoryId, - categoryName: linkDB.category.name, - }); return { props: { link: JSON.parse(JSON.stringify(link)), diff --git a/src/pages/link/remove/[lid].tsx b/src/pages/link/remove/[lid].tsx index a528d91..481e303 100644 --- a/src/pages/link/remove/[lid].tsx +++ b/src/pages/link/remove/[lid].tsx @@ -98,7 +98,7 @@ export async function getServerSideProps({ query }) { const { lid } = query; const linkDB = await prisma.link.findFirst({ where: { id: Number(lid) }, - include: { category: true }, + include: { category: true, author: true }, }); if (!linkDB) { diff --git a/src/types.d.ts b/src/types.d.ts index d5bfb0d..b8e8a38 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,9 +1,12 @@ +import { User } from "@prisma/client"; + export interface Category { id: number; name: string; links: Link[]; - nextCategoryId: number; + authorId: User["id"]; + author: User; createdAt: Date; updatedAt: Date; @@ -20,7 +23,8 @@ export interface Link { name: string; }; - nextLinkId: number; + authorId: User["id"]; + author: User; favorite: boolean; createdAt: Date; diff --git a/src/utils/api.ts b/src/utils/api.ts new file mode 100644 index 0000000..0e23587 --- /dev/null +++ b/src/utils/api.ts @@ -0,0 +1,12 @@ +import { NextApiRequest } from "next"; + +export function checkMethodAllowedOrThrow( + req: NextApiRequest, + methods: Array +) { + const isMethodAllowed = methods.includes(req.method.toLowerCase()); + if (!isMethodAllowed) { + throw new Error(`Method ${req.method} not allowed`); + } + return isMethodAllowed; +} diff --git a/src/utils/front.ts b/src/utils/front.ts index b368165..00dc492 100644 --- a/src/utils/front.ts +++ b/src/utils/front.ts @@ -5,7 +5,8 @@ import { Category, Link } from "types"; export function BuildCategory({ id, name, - nextCategoryId, + authorId, + author, links = [], createdAt, updatedAt, @@ -16,14 +17,15 @@ export function BuildCategory({ links: links.map((link) => BuildLink(link, { categoryId: id, categoryName: name }) ), - nextCategoryId, + authorId, + author, createdAt, updatedAt, }; } export function BuildLink( - { id, name, url, nextLinkId, favorite, createdAt, updatedAt }, + { id, name, url, authorId, author, favorite, createdAt, updatedAt }, { categoryId, categoryName } ): Link { return { @@ -34,7 +36,8 @@ export function BuildLink( id: categoryId, name: categoryName, }, - nextLinkId, + authorId, + author, favorite, createdAt, updatedAt, diff --git a/src/utils/session.ts b/src/utils/session.ts new file mode 100644 index 0000000..f1a4905 --- /dev/null +++ b/src/utils/session.ts @@ -0,0 +1,17 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { getServerSession } from "next-auth/next"; + +import { authOptions } from "pages/api/auth/[...nextauth]"; + +export async function getSessionOrThrow( + req: NextApiRequest, + res: NextApiResponse +) { + const session = await getServerSession(req, res, authOptions); + + if (!session) { + throw new Error("You must be connected"); + } + + return session; +}