diff --git a/src/.eslintrc.json b/src/.eslintrc.json index bffb357a..ca79b343 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -1,3 +1,11 @@ { - "extends": "next/core-web-vitals" + "extends": [ + "next/core-web-vitals", + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "root": true, + "ignorePatterns": ["node_modules/", "__tests__/"] } diff --git a/src/app/(auth)/components/index.tsx b/src/app/(auth)/components/index.tsx index bd2a1ae1..f364fefc 100644 --- a/src/app/(auth)/components/index.tsx +++ b/src/app/(auth)/components/index.tsx @@ -57,8 +57,8 @@ const Auth = ({ } else { console.log("res", res) startTransition(() => { - router.push("/new") - router.refresh() + router.push("/new") + router.refresh() }) } } diff --git a/src/app/(auth)/signin/page.tsx b/src/app/(auth)/signin/page.tsx index 05aa83bc..231a943d 100644 --- a/src/app/(auth)/signin/page.tsx +++ b/src/app/(auth)/signin/page.tsx @@ -1,8 +1,10 @@ import config from "@lib/config" import Auth from "../components" -function isGithubEnabled() { - return config.github_client_id.length && config.github_client_secret.length ? true : false +function isGithubEnabled() { + return config.github_client_id.length && config.github_client_secret.length + ? true + : false } export default function SignInPage() { diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index f0e631d4..6c392da0 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -6,11 +6,19 @@ const getPasscode = async () => { return await getRequiresPasscode() } -function isGithubEnabled() { - return config.github_client_id.length && config.github_client_secret.length ? true : false +function isGithubEnabled() { + return config.github_client_id.length && config.github_client_secret.length + ? true + : false } export default async function SignUpPage() { const requiresPasscode = await getPasscode() - return + return ( + + ) } diff --git a/src/app/(posts)/components/file-dropdown/index.tsx b/src/app/(posts)/components/file-dropdown/index.tsx index 1d8723ad..62f47b8d 100644 --- a/src/app/(posts)/components/file-dropdown/index.tsx +++ b/src/app/(posts)/components/file-dropdown/index.tsx @@ -1,17 +1,13 @@ import { Popover } from "@components/popover" import { codeFileExtensions } from "@lib/constants" import clsx from "clsx" -import type { File, PostWithFiles } from "lib/server/prisma" +import type { PostWithFiles } from "lib/server/prisma" import styles from "./dropdown.module.css" import buttonStyles from "@components/button/button.module.css" import { ChevronDown, Code, File as FileIcon } from "react-feather" import { Spinner } from "@components/spinner" import Link from "next/link" -type Item = File & { - icon: JSX.Element -} - const FileDropdown = ({ files, loading @@ -23,9 +19,7 @@ const FileDropdown = ({ return ( -
+
diff --git a/src/app/(posts)/components/preview/index.tsx b/src/app/(posts)/components/preview/index.tsx index d36915c6..4b27b6c4 100644 --- a/src/app/(posts)/components/preview/index.tsx +++ b/src/app/(posts)/components/preview/index.tsx @@ -37,7 +37,7 @@ const MarkdownPreview = ({ headers: { "Content-Type": "application/json" }, - body, + body }) if (resp.ok) { diff --git a/src/app/(posts)/new/components/new.tsx b/src/app/(posts)/new/components/new.tsx index 434116f9..cef7c11d 100644 --- a/src/app/(posts)/new/components/new.tsx +++ b/src/app/(posts)/new/components/new.tsx @@ -55,6 +55,7 @@ const Post = ({ title: doc.title, content: doc.content, id: doc.id + // eslint-disable-next-line no-mixed-spaces-and-tabs })) : [emptyDoc] @@ -300,7 +301,7 @@ const Post = ({ placeholderText="Won't expire" selected={expiresAt} showTimeInput={true} - // @ts-ignore + // @ts-expect-error fix time input type customTimeInput={} timeInputLabel="Time:" dateFormat="MM/dd/yyyy h:mm aa" diff --git a/src/app/(posts)/new/from/[id]/page.tsx b/src/app/(posts)/new/from/[id]/page.tsx index cbd2fc18..6f4e2a39 100644 --- a/src/app/(posts)/new/from/[id]/page.tsx +++ b/src/app/(posts)/new/from/[id]/page.tsx @@ -31,7 +31,7 @@ const NewFromExisting = async ({ select: { title: true, content: true, - id: true, + id: true } } } diff --git a/src/app/(posts)/new/layout.tsx b/src/app/(posts)/new/layout.tsx index 255ac4c6..6be613b1 100644 --- a/src/app/(posts)/new/layout.tsx +++ b/src/app/(posts)/new/layout.tsx @@ -1,6 +1,3 @@ -import { getCurrentUser } from "@lib/server/session" -import { redirect } from "next/navigation" - export default function NewLayout({ children }: { children: React.ReactNode }) { return <>{children} } diff --git a/src/app/(posts)/new/page.tsx b/src/app/(posts)/new/page.tsx index 74fb9adc..35ce0707 100644 --- a/src/app/(posts)/new/page.tsx +++ b/src/app/(posts)/new/page.tsx @@ -5,4 +5,4 @@ const New = () => export default New -export const dynamic = 'force-static' +export const dynamic = "force-static" diff --git a/src/app/(posts)/post/[id]/components/header/title/index.tsx b/src/app/(posts)/post/[id]/components/header/title/index.tsx index 26f4e461..10b488fc 100644 --- a/src/app/(posts)/post/[id]/components/header/title/index.tsx +++ b/src/app/(posts)/post/[id]/components/header/title/index.tsx @@ -26,9 +26,11 @@ export const PostTitle = ({ }: TitleProps) => { return ( -

+

{title}{" "} by{" "} diff --git a/src/app/(posts)/post/[id]/components/post-files/index.tsx b/src/app/(posts)/post/[id]/components/post-files/index.tsx index 7647dedc..d343c586 100644 --- a/src/app/(posts)/post/[id]/components/post-files/index.tsx +++ b/src/app/(posts)/post/[id]/components/post-files/index.tsx @@ -77,11 +77,13 @@ const PostFiles = ({ } return ( -
+
{post.files?.map(({ id, content, title, html }) => ( { content: true, updatedAt: true, title: true, - html: true, + html: true } } } diff --git a/src/app/admin/components/tables.tsx b/src/app/admin/components/tables.tsx index 2058a4ef..571dbfee 100644 --- a/src/app/admin/components/tables.tsx +++ b/src/app/admin/components/tables.tsx @@ -1,7 +1,6 @@ "use client" import Button from "@components/button" -import ButtonDropdown from "@components/button-dropdown" import { Spinner } from "@components/spinner" import { useToasts } from "@components/toasts" import { Post, User } from "@lib/server/prisma" @@ -19,7 +18,6 @@ export function UserTable({ email: string | null role: string | null displayName: string | null - }[] }) { const { setToast } = useToasts() diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index a0cefe4d..ffc7d003 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,4 +1,3 @@ -import { Spinner } from "@components/spinner" import { getAllPosts, getAllUsers } from "@lib/server/prisma" import { PostTable, UserTable } from "./components/tables" diff --git a/src/app/author/[username]/page.tsx b/src/app/author/[username]/page.tsx index 0008d2a9..a437a0a1 100644 --- a/src/app/author/[username]/page.tsx +++ b/src/app/author/[username]/page.tsx @@ -1,6 +1,8 @@ import PostList from "@components/post-list" import { getPostsByUser, getUserById } from "@lib/server/prisma" +import Image from "next/image" import { Suspense } from "react" +import { User } from "react-feather" async function PostListWrapper({ posts, @@ -28,15 +30,40 @@ export default async function UserPage({ }) { // TODO: the route should be user.name, not id const id = params.username - const user = await getUserById(id) + const user = await getUserById(id, { + image: true + }) const posts = getPostsByUser(id, true) + const Avatar = () => { + if (!user?.image) { + return + } + return ( + User avatar + ) + } return ( <> -

Public posts by {user?.displayName || "Anonymous"}

+
+

Public posts by {user?.displayName || "Anonymous"}

+ +
}> - {/* @ts-ignore because TS async JSX support is iffy */} + {/* @ts-expect-error because TS async JSX support is iffy */} diff --git a/src/app/components/header/index.tsx b/src/app/components/header/index.tsx index 73629f4a..e38c9b5a 100644 --- a/src/app/components/header/index.tsx +++ b/src/app/components/header/index.tsx @@ -60,12 +60,10 @@ const Header = () => { ) } else if (tab.href) { return ( - - + + ) } diff --git a/src/app/components/post-list/list-item.tsx b/src/app/components/post-list/list-item.tsx index 432dd96a..b6bdba51 100644 --- a/src/app/components/post-list/list-item.tsx +++ b/src/app/components/post-list/list-item.tsx @@ -129,7 +129,6 @@ const ListItem = ({
  • {getIconFromFilename(file.title)} - {file.title || "Untitled file"}
  • diff --git a/src/app/components/post-list/post-list.module.css b/src/app/components/post-list/post-list.module.css index f81421c2..4e828992 100644 --- a/src/app/components/post-list/post-list.module.css +++ b/src/app/components/post-list/post-list.module.css @@ -33,3 +33,9 @@ gap: var(--gap-half); margin-bottom: var(--gap); } + +@media (max-width: 768px) { + .container ul { + padding: 0 var(--gap); + } +} diff --git a/src/app/components/skeleton/index.tsx b/src/app/components/skeleton/index.tsx index aab0ab75..6e8b1627 100644 --- a/src/app/components/skeleton/index.tsx +++ b/src/app/components/skeleton/index.tsx @@ -3,11 +3,13 @@ import styles from "./skeleton.module.css" export default function Skeleton({ width = 100, height = 24, - borderRadius = 4, + borderRadius = 4 }: { width?: number | string - height?: number | string, + height?: number | string borderRadius?: number | string }) { - return
    + return ( +
    + ) } diff --git a/src/app/components/toasts/index.tsx b/src/app/components/toasts/index.tsx index 91dc4b4e..482e30af 100644 --- a/src/app/components/toasts/index.tsx +++ b/src/app/components/toasts/index.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client" import Toast, { Toaster } from "react-hot-toast" export type ToastType = "success" | "error" | "loading" | "default" diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7ede1614..6d05d0d2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,9 +4,9 @@ import { Providers } from "./providers" import Page from "@components/page" import { Toasts } from "@components/toasts" import Header from "@components/header" -import { Inter } from '@next/font/google'; +import { Inter } from "@next/font/google" -const inter = Inter({ subsets: ['latin'], variable: "--inter-font" }) +const inter = Inter({ subsets: ["latin"], variable: "--inter-font" }) interface RootLayoutProps { children: React.ReactNode diff --git a/src/app/settings/components/sections/profile.module.css b/src/app/settings/components/sections/profile.module.css index 16da00c2..082519c6 100644 --- a/src/app/settings/components/sections/profile.module.css +++ b/src/app/settings/components/sections/profile.module.css @@ -5,3 +5,43 @@ max-width: 300px; margin-top: var(--gap); } + +/*
    + + +
    */ +/* we want the file input to be invisible and full width but still interactive button */ +.upload { + position: relative; + display: flex; + flex-direction: column; + gap: var(--gap); + max-width: 300px; + margin-top: var(--gap); + cursor: pointer; +} + +.uploadInput { + position: absolute; + opacity: 0; + cursor: pointer; + width: 300px; + height: 37px; + cursor: pointer; +} + +.uploadButton { + width: 100%; +} + +/* hover should affect button */ +.uploadInput:hover + button { + border: 1px solid var(--fg); + +} diff --git a/src/app/settings/components/sections/profile.tsx b/src/app/settings/components/sections/profile.tsx index e85b429f..a1f02158 100644 --- a/src/app/settings/components/sections/profile.tsx +++ b/src/app/settings/components/sections/profile.tsx @@ -4,13 +4,13 @@ import Button from "@components/button" import Input from "@components/input" import Note from "@components/note" import { useToasts } from "@components/toasts" -import { User } from "next-auth" +import { useSession } from "next-auth/react" import { useState } from "react" import styles from "./profile.module.css" -const Profile = ({ user }: { user: User }) => { - // TODO: make this displayName, requires fetching user from DB as session doesnt have it - const [name, setName] = useState(user.name || "") +const Profile = () => { + const { data: session } = useSession() + const [name, setName] = useState(session?.user.displayName || "") const [submitting, setSubmitting] = useState(false) const { setToast } = useToasts() @@ -31,10 +31,10 @@ const Profile = ({ user }: { user: User }) => { setSubmitting(true) const data = { - displayName: name, + displayName: name } - const res = await fetch(`/api/user/${user.id}`, { + const res = await fetch(`/api/user/${session?.user.id}`, { method: "PUT", headers: { "Content-Type": "application/json" @@ -57,6 +57,18 @@ const Profile = ({ user }: { user: User }) => { } } + /* if we have their email, they signed in with OAuth */ + // const imageViaOauth = Boolean(session?.user.email) + + // const TooltipComponent = ({ children }: { children: React.ReactNode }) => + // imageViaOauth ? ( + // + // {children} + // + // ) : ( + // <>{children} + // ) + return ( <> @@ -83,12 +95,49 @@ const Profile = ({ user }: { user: User }) => { type="email" width={"100%"} placeholder="my@email.io" - value={user.email || undefined} + value={session?.user.email || undefined} disabled aria-label="Email" />
    - + {/*
    + + {user.image ? ( + + ) : ( + + )} + +
    + + +
    +
    +
    */} + + ) diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index ebc0da3e..52b3ae1f 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,19 +1,10 @@ import SettingsGroup from "../components/settings-group" import Profile from "app/settings/components/sections/profile" -import { authOptions } from "@lib/server/auth" -import { getCurrentUser } from "@lib/server/session" -import { redirect } from "next/navigation" export default async function SettingsPage() { - const user = await getCurrentUser() - - if (!user) { - return redirect(authOptions.pages?.signIn || "/new") - } - return ( - + ) } diff --git a/src/lib/config.ts b/src/lib/config.ts index 32541a10..7b540aa0 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -35,13 +35,13 @@ export const config = (env: Environment): Config => { return value } - const defaultIfUndefined = (str: string, defaultValue: string): string => { - const value = env[str] - if (value === undefined) { - return defaultValue - } - return value - } + // const defaultIfUndefined = (str: string, defaultValue: string): string => { + // const value = env[str] + // if (value === undefined) { + // return defaultValue + // } + // return value + // } const validNodeEnvs = (str: EnvironmentValue) => { const valid = ["development", "production", "test"] @@ -56,14 +56,6 @@ export const config = (env: Environment): Config => { const is_production = env.NODE_ENV === "production" - const developmentDefault = (name: string, defaultValue: string): string => { - if (is_production) { - return throwIfUndefined(name) - } else { - return defaultIfUndefined(name, defaultValue) - } - } - validNodeEnvs(env.NODE_ENV) throwIfUndefined("DATABASE_URL") diff --git a/src/lib/gist/fetch.ts b/src/lib/gist/fetch.ts index 694b6c9c..579bf7d5 100644 --- a/src/lib/gist/fetch.ts +++ b/src/lib/gist/fetch.ts @@ -22,7 +22,7 @@ interface File { export interface GistResponse { id: string created_at: Timestamp - description: String + description: string files: { [key: string]: File } diff --git a/src/lib/gist/types.d.ts b/src/lib/gist/types.d.ts index 832b2fc5..b2bf70da 100644 --- a/src/lib/gist/types.d.ts +++ b/src/lib/gist/types.d.ts @@ -6,6 +6,6 @@ export interface GistFile { export interface Gist { id: string created_at: Date - description: String + description: string files: GistFile[] } diff --git a/src/lib/hooks/use-trace-route.ts b/src/lib/hooks/use-trace-route.ts index ff74bfc6..81c0fe0f 100644 --- a/src/lib/hooks/use-trace-route.ts +++ b/src/lib/hooks/use-trace-route.ts @@ -1,6 +1,6 @@ import { useRef, useEffect } from "react" -function useTraceUpdate(props: { [key: string]: any }) { +function useTraceUpdate(props: { [key: string]: unknown }) { const prev = useRef(props) useEffect(() => { const changedProps = Object.entries(props).reduce((ps, [k, v]) => { @@ -8,7 +8,7 @@ function useTraceUpdate(props: { [key: string]: any }) { ps[k] = [prev.current[k], v] } return ps - }, {} as { [key: string]: any }) + }, {} as { [key: string]: unknown }) if (Object.keys(changedProps).length > 0) { console.log("Changed props:", changedProps) } diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 1921dded..77c1ae8a 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -5,10 +5,9 @@ import CredentialsProvider from "next-auth/providers/credentials" import { prisma } from "@lib/server/prisma" import config from "@lib/config" import * as crypto from "crypto" -import { Provider } from "next-auth/providers" const credentialsOptions = () => { - const options: Record = { + const options: Record = { username: { label: "Username", required: true, @@ -47,7 +46,8 @@ const providers = () => { providers.push( CredentialsProvider({ name: "Drift", - credentials: credentialsOptions(), + // @ts-expect-error TODO: fix types + credentials: credentialsOptions() as unknown, async authorize(credentials) { if (!credentials || !credentials.username || !credentials.password) { throw new Error("Missing credentials") diff --git a/src/lib/server/prisma.ts b/src/lib/server/prisma.ts index 65073619..b7ff2c9b 100644 --- a/src/lib/server/prisma.ts +++ b/src/lib/server/prisma.ts @@ -1,9 +1,10 @@ declare global { + // eslint-disable-next-line no-var var prisma: PrismaClient | undefined } import config from "@lib/config" -import { Post, PrismaClient, File, User, Prisma } from "@prisma/client" +import { Post, PrismaClient, User, Prisma } from "@prisma/client" export type { User, File, Post } from "@prisma/client" export const prisma = @@ -126,7 +127,7 @@ export async function getPostsByUser(userId: User["id"], withFiles?: boolean) { select: { id: true, title: true, - createdAt: true, + createdAt: true } } }) @@ -136,7 +137,10 @@ export async function getPostsByUser(userId: User["id"], withFiles?: boolean) { return posts } -export const getUserById = async (userId: User["id"]) => { +export const getUserById = async ( + userId: User["id"], + selects?: Prisma.UserFindUniqueArgs["select"] +) => { const user = await prisma.user.findUnique({ where: { id: userId @@ -146,7 +150,8 @@ export const getUserById = async (userId: User["id"]) => { email: true, // displayName: true, role: true, - displayName: true + displayName: true, + ...selects } }) @@ -199,7 +204,7 @@ export const getPostById = async ( postId: Post["id"], options?: GetPostByIdOptions ): Promise => { - let post = await prisma.post.findUnique({ + const post = await prisma.post.findUnique({ where: { id: postId }, diff --git a/src/lib/time-ago.ts b/src/lib/time-ago.ts index 0688d9c4..d87cbff7 100644 --- a/src/lib/time-ago.ts +++ b/src/lib/time-ago.ts @@ -11,7 +11,7 @@ const epochs = [ ] as const const getDuration = (timeAgoInSeconds: number) => { - for (let [name, seconds] of epochs) { + for (const [name, seconds] of epochs) { const interval = Math.floor(timeAgoInSeconds / seconds) if (interval >= 1) { diff --git a/src/package.json b/src/package.json index 946a6011..0a6f3865 100644 --- a/src/package.json +++ b/src/package.json @@ -6,7 +6,7 @@ "dev": "next dev --port 3000", "build": "next build", "start": "next start --port 3000", - "lint": "next lint && prettier --list-different --config .prettierrc '{components,lib,app}/**/*.{ts,tsx}' --write", + "lint": "next lint && prettier --list-different --config .prettierrc '{components,lib,app,pages}/**/*.{ts,tsx}' --write", "analyze": "cross-env ANALYZE=true next build", "find:unused": "next-unused", "prisma": "prisma", @@ -51,6 +51,8 @@ "@types/react-datepicker": "4.4.1", "@types/react-dom": "18.0.3", "@types/uuid": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^5.46.1", + "@typescript-eslint/parser": "^5.46.1", "clsx": "^1.2.1", "cross-env": "7.0.3", "csstype": "^3.1.1", diff --git a/src/pages/api/admin/index.ts b/src/pages/api/admin/index.ts index 105e28fd..3a0023e5 100644 --- a/src/pages/api/admin/index.ts +++ b/src/pages/api/admin/index.ts @@ -45,13 +45,15 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { switch (req.method) { case "GET": switch (action) { - case "users": + case "users": { const users = await prisma.user.findMany() return res.status(200).json(users) - case "posts": + } + case "posts": { const posts = await prisma.post.findMany() return res.status(200).json(posts) - case "user": + } + case "user": { const { id: userId } = req.query const user = await prisma.user.findUnique({ where: { @@ -59,7 +61,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } }) return res.status(200).json(user) - case "post": + } + case "post": { const { id: postId } = req.query const post = await prisma.post.findUnique({ where: { @@ -67,11 +70,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } }) return res.status(200).json(post) + } } break case "PATCH": switch (action) { - case "set-role": + case "set-role": { const { userId, role } = req.body if (!userId || !role || role !== "admin" || role !== "user") { return res.status(400).json({ error: "Invalid request" }) @@ -85,11 +89,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }) return res.status(200).json(user) + } } break case "DELETE": switch (action) { - case "delete-user": + case "delete-user": { const { userId } = req.body if (!userId) { return res.status(400).json({ error: "Invalid request" }) @@ -98,7 +103,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { await deleteUser(userId) return res.status(200).send("User deleted") - case "delete-post": + } + case "delete-post": { const { postId } = req.body if (!postId) { return res.status(400).json({ error: "Invalid request" }) @@ -109,6 +115,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }) return res.status(200).json(post) + } } break } diff --git a/src/pages/api/auth/requires-passcode.ts b/src/pages/api/auth/requires-passcode.ts index 38974190..1ec1e758 100644 --- a/src/pages/api/auth/requires-passcode.ts +++ b/src/pages/api/auth/requires-passcode.ts @@ -28,6 +28,8 @@ export default async function requiresPasscode( if (slug === "requires-passcode") { return handleRequiresPasscode(req, res) } + + return res.status(404).json({ error: "Not found" }) default: return res.status(405).json({ error: "Method not allowed" }) } diff --git a/src/pages/api/file/raw/[id].ts b/src/pages/api/file/raw/[id].ts index 9bd1fcfb..3715b912 100644 --- a/src/pages/api/file/raw/[id].ts +++ b/src/pages/api/file/raw/[id].ts @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next" -import {prisma} from "lib/server/prisma" +import { prisma } from "lib/server/prisma" import { parseQueryParam } from "@lib/server/parse-query-param" const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { diff --git a/src/pages/api/health.ts b/src/pages/api/health.ts index efd4dbf8..95da3084 100644 --- a/src/pages/api/health.ts +++ b/src/pages/api/health.ts @@ -1,9 +1,9 @@ import { NextApiRequest, NextApiResponse } from "next" const handler = async (_: NextApiRequest, res: NextApiResponse) => { - return res.json({ - status: "UP" - }) + return res.json({ + status: "UP" + }) } export default handler diff --git a/src/pages/api/post/[id].ts b/src/pages/api/post/[id].ts index 72a49049..68e86b80 100644 --- a/src/pages/api/post/[id].ts +++ b/src/pages/api/post/[id].ts @@ -1,6 +1,6 @@ import { withMethods } from "@lib/api-middleware/with-methods" import { parseQueryParam } from "@lib/server/parse-query-param" -import { getPostById, PostWithFiles } from "@lib/server/prisma" +import { getPostById } from "@lib/server/prisma" import type { NextApiRequest, NextApiResponse } from "next" import { getSession } from "next-auth/react" import { prisma } from "lib/server/prisma" @@ -14,7 +14,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { export default withMethods(["GET", "PUT", "DELETE"], handler) -async function handleGet(req: NextApiRequest, res: NextApiResponse) { +async function handleGet(req: NextApiRequest, res: NextApiResponse) { const id = parseQueryParam(req.query.id) if (!id) { @@ -44,7 +44,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse) { // the user can always go directly to their own post if (session?.user.id === post.authorId) { return res.json({ - ...post, + ...post }) } @@ -58,7 +58,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse) { if (hash === post.password) { return res.json({ - ...post, + ...post }) } else { return { @@ -76,7 +76,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse) { } // PUT is for adjusting visibility and password -async function handlePut(req: NextApiRequest, res: NextApiResponse) { +async function handlePut(req: NextApiRequest, res: NextApiResponse) { const { password, visibility } = req.body const id = parseQueryParam(req.query.id) @@ -124,7 +124,10 @@ async function handlePut(req: NextApiRequest, res: NextApiResponse) { }) } -async function handleDelete(req: NextApiRequest, res: NextApiResponse) { +async function handleDelete( + req: NextApiRequest, + res: NextApiResponse +) { const id = parseQueryParam(req.query.id) if (!id) { diff --git a/src/pages/api/post/index.ts b/src/pages/api/post/index.ts index 8ce856a6..7738301e 100644 --- a/src/pages/api/post/index.ts +++ b/src/pages/api/post/index.ts @@ -1,16 +1,12 @@ -// nextjs typescript api handler - import { withMethods } from "@lib/api-middleware/with-methods" import { authOptions } from "@lib/server/auth" -import { prisma, getPostById } from "@lib/server/prisma" +import { prisma } from "@lib/server/prisma" import { NextApiRequest, NextApiResponse } from "next" import { unstable_getServerSession } from "next-auth/next" import { File } from "@lib/server/prisma" import * as crypto from "crypto" import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file" -import { getSession } from "next-auth/react" -import { parseQueryParam } from "@lib/server/parse-query-param" const handler = async (req: NextApiRequest, res: NextApiResponse) => { return await handlePost(req, res) @@ -18,7 +14,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { export default withMethods(["POST"], handler) -async function handlePost(req: NextApiRequest, res: NextApiResponse) { +async function handlePost(req: NextApiRequest, res: NextApiResponse) { try { const session = await unstable_getServerSession(req, res, authOptions) if (!session || !session.user.id) { @@ -49,7 +45,7 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) { throw new Error("You must submit at least one file") } - let hashedPassword: string = "" + let hashedPassword = "" if (req.body.visibility === "protected") { hashedPassword = crypto .createHash("sha256") diff --git a/src/pages/api/post/search.ts b/src/pages/api/post/search.ts index 86051a98..550ee8e2 100644 --- a/src/pages/api/post/search.ts +++ b/src/pages/api/post/search.ts @@ -20,7 +20,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { let posts: ServerPostWithFiles[] if (session?.user.id === user || session?.user.role === "admin") { posts = await searchPosts(query, { - userId: user, + userId: user }) } else { posts = await searchPosts(query, { diff --git a/src/pages/api/user/[id].ts b/src/pages/api/user/[id].ts index e0b3f2ee..2efabde8 100644 --- a/src/pages/api/user/[id].ts +++ b/src/pages/api/user/[id].ts @@ -23,7 +23,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } switch (req.method) { - case "PUT": + case "PUT": { const { displayName } = req.body const updatedUser = await prisma.user.update({ where: { @@ -40,6 +40,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { name: updatedUser.displayName // bio: updatedUser.bio }) + } case "GET": return res.json(currUser) case "DELETE": @@ -48,7 +49,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } await deleteUser(id) - + break default: return res.status(405).json({ message: "Method not allowed" }) } @@ -61,7 +62,6 @@ export default withMethods(["GET", "PUT", "DELETE"], handler) * @warning This function does not perform any authorization checks */ export async function deleteUser(id: string | undefined) { - // first delete all of the user's posts await prisma.post.deleteMany({ where: { diff --git a/src/pages/api/user/posts.ts b/src/pages/api/user/posts.ts index e6d66a12..09dbc68b 100644 --- a/src/pages/api/user/posts.ts +++ b/src/pages/api/user/posts.ts @@ -7,7 +7,7 @@ export default async function handle( res: NextApiResponse ) { switch (req.method) { - case "GET": + case "GET": { const userId = parseQueryParam(req.query.userId) if (!userId) { return res.status(400).json({ error: "Missing userId" }) @@ -15,6 +15,7 @@ export default async function handle( const posts = await getPostsByUser(userId) return res.json(posts) + } default: return res.status(405).end() } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index 208c00bd..2c17a42d 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -18,6 +18,8 @@ specifiers: '@types/react-datepicker': 4.4.1 '@types/react-dom': 18.0.3 '@types/uuid': ^9.0.0 + '@typescript-eslint/eslint-plugin': ^5.46.1 + '@typescript-eslint/parser': ^5.46.1 '@wcj/markdown-to-html': ^2.1.2 '@wits/next-themes': 0.2.14 client-only: ^0.0.1 @@ -90,6 +92,8 @@ devDependencies: '@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y '@types/react-dom': 18.0.3 '@types/uuid': 9.0.0 + '@typescript-eslint/eslint-plugin': 5.46.1_byqm7zzsgtndvuamqqta6vngru + '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i clsx: 1.2.1 cross-env: 7.0.3 csstype: 3.1.1 @@ -1509,6 +1513,10 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: false + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + /@types/json5/0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true @@ -1583,6 +1591,10 @@ packages: /@types/scheduler/0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + /@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + dev: true + /@types/stack-utils/2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: false @@ -1605,8 +1617,35 @@ packages: '@types/yargs-parser': 21.0.0 dev: false - /@typescript-eslint/parser/5.42.1_hsmo2rtalirsvadpuxki35bq2i: - resolution: {integrity: sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==} + /@typescript-eslint/eslint-plugin/5.46.1_byqm7zzsgtndvuamqqta6vngru: + resolution: {integrity: sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i + '@typescript-eslint/scope-manager': 5.46.1 + '@typescript-eslint/type-utils': 5.46.1_hsmo2rtalirsvadpuxki35bq2i + '@typescript-eslint/utils': 5.46.1_hsmo2rtalirsvadpuxki35bq2i + debug: 4.3.4 + eslint: 8.27.0 + ignore: 5.2.0 + natural-compare-lite: 1.4.0 + regexpp: 3.2.0 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.6.4 + typescript: 4.6.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.46.1_hsmo2rtalirsvadpuxki35bq2i: + resolution: {integrity: sha512-RelQ5cGypPh4ySAtfIMBzBGyrNerQcmfA1oJvPj5f+H4jI59rl9xxpn4bonC0tQvUKOEN7eGBFWxFLK3Xepneg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1615,9 +1654,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.42.1 - '@typescript-eslint/types': 5.42.1 - '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.6.4 + '@typescript-eslint/scope-manager': 5.46.1 + '@typescript-eslint/types': 5.46.1 + '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.6.4 debug: 4.3.4 eslint: 8.27.0 typescript: 4.6.4 @@ -1625,12 +1664,32 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager/5.42.1: - resolution: {integrity: sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ==} + /@typescript-eslint/scope-manager/5.46.1: + resolution: {integrity: sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.42.1 - '@typescript-eslint/visitor-keys': 5.42.1 + '@typescript-eslint/types': 5.46.1 + '@typescript-eslint/visitor-keys': 5.46.1 + dev: true + + /@typescript-eslint/type-utils/5.46.1_hsmo2rtalirsvadpuxki35bq2i: + resolution: {integrity: sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.6.4 + '@typescript-eslint/utils': 5.46.1_hsmo2rtalirsvadpuxki35bq2i + debug: 4.3.4 + eslint: 8.27.0 + tsutils: 3.21.0_typescript@4.6.4 + typescript: 4.6.4 + transitivePeerDependencies: + - supports-color dev: true /@typescript-eslint/types/4.33.0: @@ -1638,8 +1697,8 @@ packages: engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} dev: true - /@typescript-eslint/types/5.42.1: - resolution: {integrity: sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA==} + /@typescript-eslint/types/5.46.1: + resolution: {integrity: sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -1664,8 +1723,8 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.42.1_typescript@4.6.4: - resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==} + /@typescript-eslint/typescript-estree/5.46.1_typescript@4.6.4: + resolution: {integrity: sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1673,8 +1732,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.42.1 - '@typescript-eslint/visitor-keys': 5.42.1 + '@typescript-eslint/types': 5.46.1 + '@typescript-eslint/visitor-keys': 5.46.1 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1685,6 +1744,26 @@ packages: - supports-color dev: true + /@typescript-eslint/utils/5.46.1_hsmo2rtalirsvadpuxki35bq2i: + resolution: {integrity: sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.46.1 + '@typescript-eslint/types': 5.46.1 + '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.6.4 + eslint: 8.27.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.27.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys/4.33.0: resolution: {integrity: sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -1693,11 +1772,11 @@ packages: eslint-visitor-keys: 2.1.0 dev: true - /@typescript-eslint/visitor-keys/5.42.1: - resolution: {integrity: sha512-LOQtSF4z+hejmpUvitPlc4hA7ERGoj2BVkesOcG91HCn8edLGUXbTrErmutmPbl8Bo9HjAvOO/zBKQHExXNA2A==} + /@typescript-eslint/visitor-keys/5.46.1: + resolution: {integrity: sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.42.1 + '@typescript-eslint/types': 5.46.1 eslint-visitor-keys: 3.3.0 dev: true @@ -2780,11 +2859,11 @@ packages: dependencies: '@next/eslint-plugin-next': 13.0.3 '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/parser': 5.42.1_hsmo2rtalirsvadpuxki35bq2i + '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i eslint: 8.27.0 eslint-import-resolver-node: 0.3.6 eslint-import-resolver-typescript: 2.7.1_dcpv4nbdr5ks2h5677xdltrk6e - eslint-plugin-import: 2.26.0_fjrawv2a4e2kreqduevmayjdry + eslint-plugin-import: 2.26.0_gqysc5ehwyt3mg2jls3nr3332q eslint-plugin-jsx-a11y: 6.6.1_eslint@8.27.0 eslint-plugin-react: 7.31.10_eslint@8.27.0 eslint-plugin-react-hooks: 4.6.0_eslint@8.27.0 @@ -2812,7 +2891,7 @@ packages: dependencies: debug: 4.3.4 eslint: 8.27.0 - eslint-plugin-import: 2.26.0_fjrawv2a4e2kreqduevmayjdry + eslint-plugin-import: 2.26.0_gqysc5ehwyt3mg2jls3nr3332q glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.1 @@ -2821,7 +2900,7 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.4_c5vbubjxm3sqe7zyydgtitlaga: + /eslint-module-utils/2.7.4_nw56cc7ve4sv3zakvubomgge2q: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} peerDependencies: @@ -2842,7 +2921,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.42.1_hsmo2rtalirsvadpuxki35bq2i + '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i debug: 3.2.7 eslint: 8.27.0 eslint-import-resolver-node: 0.3.6 @@ -2851,7 +2930,7 @@ packages: - supports-color dev: true - /eslint-plugin-import/2.26.0_fjrawv2a4e2kreqduevmayjdry: + /eslint-plugin-import/2.26.0_gqysc5ehwyt3mg2jls3nr3332q: resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} peerDependencies: @@ -2861,14 +2940,14 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.42.1_hsmo2rtalirsvadpuxki35bq2i + '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i array-includes: 3.1.6 array.prototype.flat: 1.3.1 debug: 2.6.9 doctrine: 2.1.0 eslint: 8.27.0 eslint-import-resolver-node: 0.3.6 - eslint-module-utils: 2.7.4_c5vbubjxm3sqe7zyydgtitlaga + eslint-module-utils: 2.7.4_nw56cc7ve4sv3zakvubomgge2q has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -2936,6 +3015,14 @@ packages: string.prototype.matchall: 4.0.8 dev: true + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + /eslint-scope/7.1.1: resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3040,6 +3127,11 @@ packages: estraverse: 5.3.0 dev: true + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + /estraverse/5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -5102,6 +5194,10 @@ packages: dev: false optional: true + /natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}