Add eslint configs, fix lint errors

This commit is contained in:
Max Leiter 2022-12-18 18:18:32 -08:00
parent 631f98aaaf
commit 19c5725847
45 changed files with 388 additions and 164 deletions

View file

@ -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__/"]
}

View file

@ -2,7 +2,9 @@ import config from "@lib/config"
import Auth from "../components"
function isGithubEnabled() {
return config.github_client_id.length && config.github_client_secret.length ? true : false
return config.github_client_id.length && config.github_client_secret.length
? true
: false
}
export default function SignInPage() {

View file

@ -7,10 +7,18 @@ const getPasscode = async () => {
}
function isGithubEnabled() {
return config.github_client_id.length && config.github_client_secret.length ? true : false
return config.github_client_id.length && config.github_client_secret.length
? true
: false
}
export default async function SignUpPage() {
const requiresPasscode = await getPasscode()
return <Auth page="signup" requiresServerPassword={requiresPasscode} isGithubEnabled={isGithubEnabled()} />
return (
<Auth
page="signup"
requiresServerPassword={requiresPasscode}
isGithubEnabled={isGithubEnabled()}
/>
)
}

View file

@ -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 (
<Popover>
<Popover.Trigger className={buttonStyles.button}>
<div
style={{ minWidth: 125 }}
>
<div style={{ minWidth: 125 }}>
<Spinner />
</div>
</Popover.Trigger>

View file

@ -37,7 +37,7 @@ const MarkdownPreview = ({
headers: {
"Content-Type": "application/json"
},
body,
body
})
if (resp.ok) {

View file

@ -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={<CustomTimeInput />}
timeInputLabel="Time:"
dateFormat="MM/dd/yyyy h:mm aa"

View file

@ -31,7 +31,7 @@ const NewFromExisting = async ({
select: {
title: true,
content: true,
id: true,
id: true
}
}
}

View file

@ -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}</>
}

View file

@ -5,4 +5,4 @@ const New = () => <NewPost />
export default New
export const dynamic = 'force-static'
export const dynamic = "force-static"

View file

@ -26,9 +26,11 @@ export const PostTitle = ({
}: TitleProps) => {
return (
<span className={styles.title}>
<h1 style={{
<h1
style={{
fontSize: "1.175rem"
}}>
}}
>
{title}{" "}
<span style={{ color: "var(--gray)" }}>
by{" "}

View file

@ -77,11 +77,13 @@ const PostFiles = ({
}
return (
<main style={{
<main
style={{
display: "flex",
flexDirection: "column",
gap: "var(--gap-double)"
}}>
}}
>
{post.files?.map(({ id, content, title, html }) => (
<DocumentComponent
skeleton={false}

View file

@ -1,15 +1,14 @@
"use client"
import { memo, useEffect } from "react"
import styles from "./document.module.css"
import Skeleton from "@components/skeleton"
import Link from "next/link"
import Tooltip from "@components/tooltip"
import Button from "@components/button"
import ButtonGroup from "@components/button-group"
import Skeleton from "@components/skeleton"
import Tooltip from "@components/tooltip"
import DocumentTabs from "app/(posts)/components/tabs"
import Link from "next/link"
import { memo } from "react"
import { Download, ExternalLink } from "react-feather"
import styles from "./document.module.css"
type SharedProps = {
title?: string
@ -22,9 +21,11 @@ type SharedProps = {
type Props = (
| {
skeleton?: true
// eslint-disable-next-line no-mixed-spaces-and-tabs
}
| {
skeleton?: false
// eslint-disable-next-line no-mixed-spaces-and-tabs
}
) &
SharedProps

View file

@ -1,9 +1,5 @@
import { notFound, redirect } from "next/navigation"
import {
getPostById,
Post,
PostWithFilesAndAuthor
} from "@lib/server/prisma"
import { getPostById, Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
import { getCurrentUser } from "@lib/server/session"
import ScrollToTop from "@components/scroll-to-top"
import { title } from "process"

View file

@ -27,7 +27,7 @@ const getPost = async (id: string) => {
content: true,
updatedAt: true,
title: true,
html: true,
html: true
}
}
}

View file

@ -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()

View file

@ -1,4 +1,3 @@
import { Spinner } from "@components/spinner"
import { getAllPosts, getAllUsers } from "@lib/server/prisma"
import { PostTable, UserTable } from "./components/tables"

View file

@ -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 <User />
}
return (
<Image
src={user.image}
alt="User avatar"
className="w-12 h-12 rounded-full"
width={48}
height={48}
/>
)
}
return (
<>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
>
<h1>Public posts by {user?.displayName || "Anonymous"}</h1>
<Avatar />
</div>
<Suspense fallback={<PostList initialPosts={JSON.stringify({})} />}>
{/* @ts-ignore because TS async JSX support is iffy */}
{/* @ts-expect-error because TS async JSX support is iffy */}
<PostListWrapper posts={posts} userId={id} />
</Suspense>
</>

View file

@ -60,12 +60,10 @@ const Header = () => {
)
} else if (tab.href) {
return (
<Link
key={tab.value}
href={tab.href}
data-tab={tab.value}
>
<Button className={activeStyle} iconLeft={tab.icon}>{tab.name ? tab.name : undefined}</Button>
<Link key={tab.value} href={tab.href} data-tab={tab.value}>
<Button className={activeStyle} iconLeft={tab.icon}>
{tab.name ? tab.name : undefined}
</Button>
</Link>
)
}

View file

@ -129,7 +129,6 @@ const ListItem = ({
<li key={file.id}>
<Link colored href={`/post/${post.id}#${file.title}`}>
{getIconFromFilename(file.title)}
{file.title || "Untitled file"}
</Link>
</li>

View file

@ -33,3 +33,9 @@
gap: var(--gap-half);
margin-bottom: var(--gap);
}
@media (max-width: 768px) {
.container ul {
padding: 0 var(--gap);
}
}

View file

@ -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 <div className={styles.skeleton} style={{ width, height, borderRadius }} />
return (
<div className={styles.skeleton} style={{ width, height, borderRadius }} />
)
}

View file

@ -1,4 +1,4 @@
"use client";
"use client"
import Toast, { Toaster } from "react-hot-toast"
export type ToastType = "success" | "error" | "loading" | "default"

View file

@ -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

View file

@ -5,3 +5,43 @@
max-width: 300px;
margin-top: var(--gap);
}
/* <div className={styles.upload}>
<input
type="file"
disabled={imageViaOauth}
className={styles.uploadInput}
/>
<Button type="button" disabled={imageViaOauth} width="100%" className={styles.uploadButton}>
Upload
</Button>
</div> */
/* 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);
}

View file

@ -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<string>(user.name || "")
const Profile = () => {
const { data: session } = useSession()
const [name, setName] = useState<string>(session?.user.displayName || "")
const [submitting, setSubmitting] = useState<boolean>(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 ? (
// <Tooltip content="Change your profile image on your OAuth provider">
// {children}
// </Tooltip>
// ) : (
// <>{children}</>
// )
return (
<>
<Note type="warning">
@ -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"
/>
</div>
<Button type="submit" loading={submitting}>Submit</Button>
{/* <div>
<label htmlFor="image">User Avatar</label>
{user.image ? (
<Input
id="image"
type="file"
width={"100%"}
placeholder="my image"
disabled
aria-label="Image"
src={user.image}
/>
) : (
<UserIcon />
)}
<TooltipComponent>
<div className={styles.upload}>
<input
type="file"
disabled={imageViaOauth}
className={styles.uploadInput}
/>
<Button
type="button"
disabled={imageViaOauth}
width="100%"
className={styles.uploadButton}
aria-hidden="true"
>
Upload
</Button>
</div>
</TooltipComponent>
</div> */}
<Button type="submit" loading={submitting}>
Submit
</Button>
</form>
</>
)

View file

@ -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 (
<SettingsGroup title="Profile">
<Profile user={user} />
<Profile />
</SettingsGroup>
)
}

View file

@ -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")

View file

@ -22,7 +22,7 @@ interface File {
export interface GistResponse {
id: string
created_at: Timestamp
description: String
description: string
files: {
[key: string]: File
}

View file

@ -6,6 +6,6 @@ export interface GistFile {
export interface Gist {
id: string
created_at: Date
description: String
description: string
files: GistFile[]
}

View file

@ -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)
}

View file

@ -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<string, any> = {
const options: Record<string, unknown> = {
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")

View file

@ -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<Post | PostWithFiles | PostWithFilesAndAuthor | null> => {
let post = await prisma.post.findUnique({
const post = await prisma.post.findUnique({
where: {
id: postId
},

View file

@ -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) {

View file

@ -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",

View file

@ -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: {
@ -68,10 +71,11 @@ 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" })
@ -86,10 +90,11 @@ 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" })
@ -110,6 +116,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(200).json(post)
}
}
break
}
}

View file

@ -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" })
}

View file

@ -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) => {

View file

@ -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<any>) {
async function handleGet(req: NextApiRequest, res: NextApiResponse<unknown>) {
const id = parseQueryParam(req.query.id)
if (!id) {
@ -44,7 +44,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse<any>) {
// 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<any>) {
if (hash === post.password) {
return res.json({
...post,
...post
})
} else {
return {
@ -76,7 +76,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse<any>) {
}
// PUT is for adjusting visibility and password
async function handlePut(req: NextApiRequest, res: NextApiResponse<any>) {
async function handlePut(req: NextApiRequest, res: NextApiResponse<unknown>) {
const { password, visibility } = req.body
const id = parseQueryParam(req.query.id)
@ -124,7 +124,10 @@ async function handlePut(req: NextApiRequest, res: NextApiResponse<any>) {
})
}
async function handleDelete(req: NextApiRequest, res: NextApiResponse<any>) {
async function handleDelete(
req: NextApiRequest,
res: NextApiResponse<unknown>
) {
const id = parseQueryParam(req.query.id)
if (!id) {

View file

@ -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<any>) {
async function handlePost(req: NextApiRequest, res: NextApiResponse<unknown>) {
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<any>) {
throw new Error("You must submit at least one file")
}
let hashedPassword: string = ""
let hashedPassword = ""
if (req.body.visibility === "protected") {
hashedPassword = crypto
.createHash("sha256")

View file

@ -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, {

View file

@ -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: {

View file

@ -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()
}

View file

@ -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==}