From fe51aa2376c8452bc4bf13103502bbb218def96e Mon Sep 17 00:00:00 2001 From: Bart Huijgen Date: Sun, 17 Jan 2021 22:15:41 +0100 Subject: [PATCH] add handler wrappers for getServerSideProps are api routes to clean up code reuse --- .../src/pages/api/blueprint/create.ts | 18 ++--- apps/blueprints/src/pages/api/login.ts | 15 ++-- apps/blueprints/src/pages/api/logout.ts | 20 ++---- apps/blueprints/src/pages/api/register.ts | 14 ++-- apps/blueprints/src/pages/api/user.ts | 35 ++++----- apps/blueprints/src/pages/api/user/edit.ts | 71 ++++++++----------- .../blueprints/src/pages/api/user/favorite.ts | 43 ++++------- .../src/pages/blueprint/[blueprintId].tsx | 39 +++++----- apps/blueprints/src/utils/api-handler.ts | 29 ++++++++ apps/blueprints/src/utils/page-handler.ts | 27 +++++++ 10 files changed, 154 insertions(+), 157 deletions(-) create mode 100644 apps/blueprints/src/utils/api-handler.ts create mode 100644 apps/blueprints/src/utils/page-handler.ts diff --git a/apps/blueprints/src/pages/api/blueprint/create.ts b/apps/blueprints/src/pages/api/blueprint/create.ts index 425c7dd..13af3b5 100644 --- a/apps/blueprints/src/pages/api/blueprint/create.ts +++ b/apps/blueprints/src/pages/api/blueprint/create.ts @@ -1,23 +1,13 @@ -import { NextApiHandler } from "next"; import { - init, createBlueprint, createBlueprintPage, createBlueprintBook, - getSessionByToken, } from "@factorio-sites/database"; -import { getSessionToken, parseBlueprintString } from "@factorio-sites/node-utils"; +import { parseBlueprintString } from "@factorio-sites/node-utils"; import { parseSequelizeError } from "../../../utils/api.utils"; +import { apiHandler } from "../../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { - await init(); - - const token = getSessionToken(req); - if (!token) { - return res.status(401).json({ status: "Not authenticated" }); - } - - const session = await getSessionByToken(token); +const handler = apiHandler(async (req, res, { session }) => { if (!session) { return res.status(401).json({ status: "Not authenticated" }); } @@ -66,6 +56,6 @@ const handler: NextApiHandler = async (req, res) => { } res.status(500).json({ status: "Failed to create blueprint" }); -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/api/login.ts b/apps/blueprints/src/pages/api/login.ts index e2c9fb8..1910d9c 100644 --- a/apps/blueprints/src/pages/api/login.ts +++ b/apps/blueprints/src/pages/api/login.ts @@ -1,13 +1,14 @@ -import { NextApiHandler } from "next"; -import { init, loginUserWithEmail } from "@factorio-sites/database"; +import { loginUserWithEmail } from "@factorio-sites/database"; import { setUserToken } from "@factorio-sites/node-utils"; +import { apiHandler } from "../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { - await init(); +const handler = apiHandler(async (req, res, { session, ip, useragent }) => { + if (session) { + return res.status(400).json({ status: "Already logged in" }); + } const { email, password } = req.body; - const ip = (req.headers["x-forwarded-for"] || (req as any).ip) as string; - const useragent = req.headers["user-agent"] as string; + const user = await loginUserWithEmail({ email, password, useragent, ip }); if (user && user.session) { @@ -16,6 +17,6 @@ const handler: NextApiHandler = async (req, res) => { } else { res.status(401).json({ status: "Invalid email and password combination" }); } -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/api/logout.ts b/apps/blueprints/src/pages/api/logout.ts index 0fd6b76..b036003 100644 --- a/apps/blueprints/src/pages/api/logout.ts +++ b/apps/blueprints/src/pages/api/logout.ts @@ -1,22 +1,14 @@ -import { NextApiHandler } from "next"; -import { init, getSessionByToken } from "@factorio-sites/database"; -import { deleteSessionToken, getSessionToken } from "@factorio-sites/node-utils"; +import { deleteSessionToken } from "@factorio-sites/node-utils"; +import { apiHandler } from "../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { - await init(); - - const token = getSessionToken(req); - - if (token) { - const session = await getSessionByToken(token); - if (session) { - await session.destroy(); - } +const handler = apiHandler(async (req, res, { session }) => { + if (session) { + await session.destroy(); deleteSessionToken(res); } res.setHeader("Location", req.query.redirect || "/"); res.status(302).end(); -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/api/register.ts b/apps/blueprints/src/pages/api/register.ts index cee16a9..0d1f293 100644 --- a/apps/blueprints/src/pages/api/register.ts +++ b/apps/blueprints/src/pages/api/register.ts @@ -1,14 +1,14 @@ -import { NextApiHandler } from "next"; -import { init, createUserWithEmail, createSession } from "@factorio-sites/database"; +import { createUserWithEmail, createSession } from "@factorio-sites/database"; import { setUserToken } from "@factorio-sites/node-utils"; import { parseSequelizeError } from "../../utils/api.utils"; +import { apiHandler } from "../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { - await init(); +const handler = apiHandler(async (req, res, { session, ip, useragent }) => { + if (session) { + return res.status(400).json({ status: "Already logged in" }); + } const { email, username, password, password_confirm } = req.body; - const ip = (req.headers["x-forwarded-for"] || (req as any).ip) as string; - const useragent = req.headers["user-agent"] as string; // Validation const errors: Record = {}; @@ -37,6 +37,6 @@ const handler: NextApiHandler = async (req, res) => { } res.status(401).json({ status: "Failed to register account" }); -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/api/user.ts b/apps/blueprints/src/pages/api/user.ts index 71b8653..5a6113f 100644 --- a/apps/blueprints/src/pages/api/user.ts +++ b/apps/blueprints/src/pages/api/user.ts @@ -1,32 +1,21 @@ -import { NextApiHandler } from "next"; -import { deleteSessionToken, getSessionToken } from "@factorio-sites/node-utils"; -import { init, getSessionByToken } from "@factorio-sites/database"; import { AuthContextProps } from "../../providers/auth"; +import { apiHandler } from "../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { - await init(); - - const session_token = getSessionToken(req); - - if (session_token) { - const session = await getSessionByToken(session_token); - if (session) { - return res.status(200).json({ - auth: { - user_id: session.user.get("id"), - username: session.user.get("username"), - email: session.user.get("email"), - steam_id: session.user.get("steam_id"), - } as AuthContextProps, - }); - } else { - deleteSessionToken(res); - } +const handler = apiHandler(async (_, res, { session }) => { + if (session) { + return res.status(200).json({ + auth: { + user_id: session.user.get("id"), + username: session.user.get("username"), + email: session.user.get("email"), + steam_id: session.user.get("steam_id"), + } as AuthContextProps, + }); } res.status(404).json({ auth: null, }); -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/api/user/edit.ts b/apps/blueprints/src/pages/api/user/edit.ts index 6d0a0d3..1d53210 100644 --- a/apps/blueprints/src/pages/api/user/edit.ts +++ b/apps/blueprints/src/pages/api/user/edit.ts @@ -1,56 +1,45 @@ -import { NextApiHandler } from "next"; -import { deleteSessionToken, getSessionToken } from "@factorio-sites/node-utils"; -import { init, getSessionByToken } from "@factorio-sites/database"; import { AuthContextProps } from "../../../providers/auth"; import { parseSequelizeError } from "../../../utils/api.utils"; +import { apiHandler } from "../../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { +const handler = apiHandler(async (req, res, { session }) => { if (req.method !== "POST") return res.status(400).json({ error: "method must be POST" }); - await init(); - const { username, email } = req.body; - const session_token = getSessionToken(req); - - if (session_token) { - const session = await getSessionByToken(session_token); - if (session) { - const user = session.user; - if (username) { - user.set("username", username); - } - if (email) { - user.set("email", email); - } else if (user.get("email") && user.get("steam_id")) { - // User currently has email but wants to delete it, allow if steam_id exists - user.set("email", null); - } - try { - await user.save(); - } catch (reason) { - const insert_errors = parseSequelizeError(reason); - if (insert_errors) { - return res.status(400).json({ errors: insert_errors }); - } - } - - return res.status(200).json({ - auth: { - user_id: session.user.get("id"), - username: session.user.get("username"), - email: session.user.get("email"), - steam_id: session.user.get("steam_id"), - } as AuthContextProps, - }); - } else { - deleteSessionToken(res); + if (session) { + const user = session.user; + if (username) { + user.set("username", username); } + if (email) { + user.set("email", email); + } else if (user.get("email") && user.get("steam_id")) { + // User currently has email but wants to delete it, allow if steam_id exists + user.set("email", null); + } + try { + await user.save(); + } catch (reason) { + const insert_errors = parseSequelizeError(reason); + if (insert_errors) { + return res.status(400).json({ errors: insert_errors }); + } + } + + return res.status(200).json({ + auth: { + user_id: session.user.get("id"), + username: session.user.get("username"), + email: session.user.get("email"), + steam_id: session.user.get("steam_id"), + } as AuthContextProps, + }); } res.status(404).json({ auth: null, }); -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/api/user/favorite.ts b/apps/blueprints/src/pages/api/user/favorite.ts index 9976a75..db6840d 100644 --- a/apps/blueprints/src/pages/api/user/favorite.ts +++ b/apps/blueprints/src/pages/api/user/favorite.ts @@ -1,45 +1,30 @@ -import { NextApiHandler } from "next"; -import { deleteSessionToken, getSessionToken } from "@factorio-sites/node-utils"; -import { - init, - getSessionByToken, - isBlueprintPageUserFavorite, - createUserFavorite, -} from "@factorio-sites/database"; +import { isBlueprintPageUserFavorite, createUserFavorite } from "@factorio-sites/database"; +import { apiHandler } from "../../../utils/api-handler"; -const handler: NextApiHandler = async (req, res) => { +const handler = apiHandler(async (req, res, { session }) => { if (req.method !== "POST") return res.status(400).json({ error: "method must be POST" }); - await init(); - const { blueprint_page_id } = req.body; - const session_token = getSessionToken(req); + if (session) { + const user = session.user; - if (session_token) { - const session = await getSessionByToken(session_token); - if (session) { - const user = session.user; + const existing = await isBlueprintPageUserFavorite(user.id, blueprint_page_id); - const existing = await isBlueprintPageUserFavorite(user.id, blueprint_page_id); - - if (existing) { - await existing.destroy(); - } else { - await createUserFavorite(user.id, blueprint_page_id); - } - - return res.status(200).json({ - favorite: !existing, - }); + if (existing) { + await existing.destroy(); } else { - deleteSessionToken(res); + await createUserFavorite(user.id, blueprint_page_id); } + + return res.status(200).json({ + favorite: !existing, + }); } res.status(404).json({ auth: null, }); -}; +}); export default handler; diff --git a/apps/blueprints/src/pages/blueprint/[blueprintId].tsx b/apps/blueprints/src/pages/blueprint/[blueprintId].tsx index 51f73bf..224172d 100644 --- a/apps/blueprints/src/pages/blueprint/[blueprintId].tsx +++ b/apps/blueprints/src/pages/blueprint/[blueprintId].tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { NextPage, NextPageContext } from "next"; +import { NextPage } from "next"; import BBCode from "bbcode-to-react"; import { Button, Grid, Image, Box } from "@chakra-ui/react"; import { @@ -9,8 +9,6 @@ import { getBlueprintBookById, getBlueprintById, getBlueprintPageById, - init, - getSessionByToken, isBlueprintPageUserFavorite, } from "@factorio-sites/database"; import { BlueprintStringData, timeLogger } from "@factorio-sites/common-utils"; @@ -20,7 +18,8 @@ import { Markdown } from "../../components/Markdown"; import { BookChildTree } from "../../components/BookChildTree"; import { CopyButton } from "../../components/CopyButton"; import { ImageEditor } from "../../components/ImageEditor"; -import { getSessionToken } from "@factorio-sites/node-utils"; +import { useAuth } from "../../providers/auth"; +import { pageHandler } from "../../utils/page-handler"; type Selected = | { type: "blueprint"; data: Pick } @@ -43,6 +42,7 @@ export const Index: NextPage = ({ blueprint_page, favorite, }) => { + const auth = useAuth(); // const [imageZoom, setImageZoom] = useState(false); const [blueprintString, setBlueprintString] = useState(null); const [data, setData] = useState(null); @@ -121,11 +121,13 @@ export const Index: NextPage = ({ gap={6} > - - - + {auth && ( + + + + )} {blueprint_book ? ( <>
This string contains a blueprint book
@@ -271,14 +273,12 @@ export const Index: NextPage = ({ ); }; -export async function getServerSideProps(context: NextPageContext) { - await init(); - +export const getServerSideProps = pageHandler(async (context, { session }) => { const throwError = (message: string) => { if (!blueprint_page && context.res) { context.res.statusCode = 404; context.res.end(JSON.stringify({ error: message })); - return {}; + return { props: {} }; } }; @@ -339,14 +339,9 @@ export async function getServerSideProps(context: NextPageContext) { // const image_exists = // selected.type === "blueprint" ? await hasBlueprintImage(selected.data.image_hash) : false; - let favorite = false; - const session_token = getSessionToken(context.req); - if (session_token) { - const session = await getSessionByToken(session_token); - favorite = session - ? !!(await isBlueprintPageUserFavorite(session.user.id, blueprint_page.id)) - : false; - } + const favorite = session + ? !!(await isBlueprintPageUserFavorite(session.user.id, blueprint_page.id)) + : false; return { props: { @@ -358,6 +353,6 @@ export async function getServerSideProps(context: NextPageContext) { favorite, } as IndexProps, }; -} +}); export default Index; diff --git a/apps/blueprints/src/utils/api-handler.ts b/apps/blueprints/src/utils/api-handler.ts new file mode 100644 index 0000000..edc789c --- /dev/null +++ b/apps/blueprints/src/utils/api-handler.ts @@ -0,0 +1,29 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { getSessionByToken, init } from "@factorio-sites/database"; +import { deleteSessionToken, getSessionToken } from "@factorio-sites/node-utils"; + +type Await = T extends PromiseLike ? Await : T; + +interface CustomContext { + session: Await>; + ip: string; + useragent: string; +} + +export const apiHandler = ( + fn: (req: NextApiRequest, res: NextApiResponse, ctx: CustomContext) => Promise +) => async (req: NextApiRequest, res: NextApiResponse) => { + await init(); + + const ip = (req.headers["x-forwarded-for"] || (req as any).ip) as string; + const useragent = req.headers["user-agent"] as string; + + const session_token = getSessionToken(req); + const session = session_token ? await getSessionByToken(session_token) : null; + + if (session_token && !session) { + deleteSessionToken(res); + } + + return fn(req, res, { session, ip, useragent }); +}; diff --git a/apps/blueprints/src/utils/page-handler.ts b/apps/blueprints/src/utils/page-handler.ts new file mode 100644 index 0000000..199ca62 --- /dev/null +++ b/apps/blueprints/src/utils/page-handler.ts @@ -0,0 +1,27 @@ +import { NextPageContext } from "next"; +import { getSessionByToken, init } from "@factorio-sites/database"; +import { getSessionToken } from "@factorio-sites/node-utils"; + +interface GetServerSidePropsReturn { + props: Record; +} + +type Await = T extends PromiseLike ? Await : T; + +interface CustomContext { + session: Await>; +} + +export const pageHandler = ( + fn: ( + context: NextPageContext, + ctx: CustomContext + ) => Promise +) => async (context: NextPageContext) => { + await init(); + + const session_token = getSessionToken(context.req); + const session = session_token ? await getSessionByToken(session_token) : null; + + return fn(context, { session }); +};