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

@ -57,8 +57,8 @@ const Auth = ({
} else { } else {
console.log("res", res) console.log("res", res)
startTransition(() => { startTransition(() => {
router.push("/new") router.push("/new")
router.refresh() router.refresh()
}) })
} }
} }

View file

@ -1,8 +1,10 @@
import config from "@lib/config" import config from "@lib/config"
import Auth from "../components" import Auth from "../components"
function isGithubEnabled() { 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() { export default function SignInPage() {

View file

@ -6,11 +6,19 @@ const getPasscode = async () => {
return await getRequiresPasscode() return await getRequiresPasscode()
} }
function isGithubEnabled() { 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() { export default async function SignUpPage() {
const requiresPasscode = await getPasscode() 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 { Popover } from "@components/popover"
import { codeFileExtensions } from "@lib/constants" import { codeFileExtensions } from "@lib/constants"
import clsx from "clsx" 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 styles from "./dropdown.module.css"
import buttonStyles from "@components/button/button.module.css" import buttonStyles from "@components/button/button.module.css"
import { ChevronDown, Code, File as FileIcon } from "react-feather" import { ChevronDown, Code, File as FileIcon } from "react-feather"
import { Spinner } from "@components/spinner" import { Spinner } from "@components/spinner"
import Link from "next/link" import Link from "next/link"
type Item = File & {
icon: JSX.Element
}
const FileDropdown = ({ const FileDropdown = ({
files, files,
loading loading
@ -23,9 +19,7 @@ const FileDropdown = ({
return ( return (
<Popover> <Popover>
<Popover.Trigger className={buttonStyles.button}> <Popover.Trigger className={buttonStyles.button}>
<div <div style={{ minWidth: 125 }}>
style={{ minWidth: 125 }}
>
<Spinner /> <Spinner />
</div> </div>
</Popover.Trigger> </Popover.Trigger>

View file

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

View file

@ -55,6 +55,7 @@ const Post = ({
title: doc.title, title: doc.title,
content: doc.content, content: doc.content,
id: doc.id id: doc.id
// eslint-disable-next-line no-mixed-spaces-and-tabs
})) }))
: [emptyDoc] : [emptyDoc]
@ -300,7 +301,7 @@ const Post = ({
placeholderText="Won't expire" placeholderText="Won't expire"
selected={expiresAt} selected={expiresAt}
showTimeInput={true} showTimeInput={true}
// @ts-ignore // @ts-expect-error fix time input type
customTimeInput={<CustomTimeInput />} customTimeInput={<CustomTimeInput />}
timeInputLabel="Time:" timeInputLabel="Time:"
dateFormat="MM/dd/yyyy h:mm aa" dateFormat="MM/dd/yyyy h:mm aa"

View file

@ -31,7 +31,7 @@ const NewFromExisting = async ({
select: { select: {
title: true, title: true,
content: 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 }) { export default function NewLayout({ children }: { children: React.ReactNode }) {
return <>{children}</> return <>{children}</>
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
"use client" "use client"
import Button from "@components/button" import Button from "@components/button"
import ButtonDropdown from "@components/button-dropdown"
import { Spinner } from "@components/spinner" import { Spinner } from "@components/spinner"
import { useToasts } from "@components/toasts" import { useToasts } from "@components/toasts"
import { Post, User } from "@lib/server/prisma" import { Post, User } from "@lib/server/prisma"
@ -19,7 +18,6 @@ export function UserTable({
email: string | null email: string | null
role: string | null role: string | null
displayName: string | null displayName: string | null
}[] }[]
}) { }) {
const { setToast } = useToasts() const { setToast } = useToasts()

View file

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

View file

@ -1,6 +1,8 @@
import PostList from "@components/post-list" import PostList from "@components/post-list"
import { getPostsByUser, getUserById } from "@lib/server/prisma" import { getPostsByUser, getUserById } from "@lib/server/prisma"
import Image from "next/image"
import { Suspense } from "react" import { Suspense } from "react"
import { User } from "react-feather"
async function PostListWrapper({ async function PostListWrapper({
posts, posts,
@ -28,15 +30,40 @@ export default async function UserPage({
}) { }) {
// TODO: the route should be user.name, not id // TODO: the route should be user.name, not id
const id = params.username const id = params.username
const user = await getUserById(id) const user = await getUserById(id, {
image: true
})
const posts = getPostsByUser(id, 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 ( return (
<> <>
<h1>Public posts by {user?.displayName || "Anonymous"}</h1> <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({})} />}> <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} /> <PostListWrapper posts={posts} userId={id} />
</Suspense> </Suspense>
</> </>

View file

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

View file

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

View file

@ -33,3 +33,9 @@
gap: var(--gap-half); gap: var(--gap-half);
margin-bottom: var(--gap); 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({ export default function Skeleton({
width = 100, width = 100,
height = 24, height = 24,
borderRadius = 4, borderRadius = 4
}: { }: {
width?: number | string width?: number | string
height?: number | string, height?: number | string
borderRadius?: 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" import Toast, { Toaster } from "react-hot-toast"
export type ToastType = "success" | "error" | "loading" | "default" export type ToastType = "success" | "error" | "loading" | "default"

View file

@ -4,9 +4,9 @@ import { Providers } from "./providers"
import Page from "@components/page" import Page from "@components/page"
import { Toasts } from "@components/toasts" import { Toasts } from "@components/toasts"
import Header from "@components/header" 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 { interface RootLayoutProps {
children: React.ReactNode children: React.ReactNode

View file

@ -5,3 +5,43 @@
max-width: 300px; max-width: 300px;
margin-top: var(--gap); 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 Input from "@components/input"
import Note from "@components/note" import Note from "@components/note"
import { useToasts } from "@components/toasts" import { useToasts } from "@components/toasts"
import { User } from "next-auth" import { useSession } from "next-auth/react"
import { useState } from "react" import { useState } from "react"
import styles from "./profile.module.css" import styles from "./profile.module.css"
const Profile = ({ user }: { user: User }) => { const Profile = () => {
// TODO: make this displayName, requires fetching user from DB as session doesnt have it const { data: session } = useSession()
const [name, setName] = useState<string>(user.name || "") const [name, setName] = useState<string>(session?.user.displayName || "")
const [submitting, setSubmitting] = useState<boolean>(false) const [submitting, setSubmitting] = useState<boolean>(false)
const { setToast } = useToasts() const { setToast } = useToasts()
@ -31,10 +31,10 @@ const Profile = ({ user }: { user: User }) => {
setSubmitting(true) setSubmitting(true)
const data = { 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", method: "PUT",
headers: { headers: {
"Content-Type": "application/json" "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 ( return (
<> <>
<Note type="warning"> <Note type="warning">
@ -83,12 +95,49 @@ const Profile = ({ user }: { user: User }) => {
type="email" type="email"
width={"100%"} width={"100%"}
placeholder="my@email.io" placeholder="my@email.io"
value={user.email || undefined} value={session?.user.email || undefined}
disabled disabled
aria-label="Email" aria-label="Email"
/> />
</div> </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> </form>
</> </>
) )

View file

@ -1,19 +1,10 @@
import SettingsGroup from "../components/settings-group" import SettingsGroup from "../components/settings-group"
import Profile from "app/settings/components/sections/profile" 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() { export default async function SettingsPage() {
const user = await getCurrentUser()
if (!user) {
return redirect(authOptions.pages?.signIn || "/new")
}
return ( return (
<SettingsGroup title="Profile"> <SettingsGroup title="Profile">
<Profile user={user} /> <Profile />
</SettingsGroup> </SettingsGroup>
) )
} }

View file

@ -35,13 +35,13 @@ export const config = (env: Environment): Config => {
return value return value
} }
const defaultIfUndefined = (str: string, defaultValue: string): string => { // const defaultIfUndefined = (str: string, defaultValue: string): string => {
const value = env[str] // const value = env[str]
if (value === undefined) { // if (value === undefined) {
return defaultValue // return defaultValue
} // }
return value // return value
} // }
const validNodeEnvs = (str: EnvironmentValue) => { const validNodeEnvs = (str: EnvironmentValue) => {
const valid = ["development", "production", "test"] const valid = ["development", "production", "test"]
@ -56,14 +56,6 @@ export const config = (env: Environment): Config => {
const is_production = env.NODE_ENV === "production" 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) validNodeEnvs(env.NODE_ENV)
throwIfUndefined("DATABASE_URL") throwIfUndefined("DATABASE_URL")

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { useRef, useEffect } from "react" import { useRef, useEffect } from "react"
function useTraceUpdate(props: { [key: string]: any }) { function useTraceUpdate(props: { [key: string]: unknown }) {
const prev = useRef(props) const prev = useRef(props)
useEffect(() => { useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => { 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] ps[k] = [prev.current[k], v]
} }
return ps return ps
}, {} as { [key: string]: any }) }, {} as { [key: string]: unknown })
if (Object.keys(changedProps).length > 0) { if (Object.keys(changedProps).length > 0) {
console.log("Changed props:", changedProps) 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 { prisma } from "@lib/server/prisma"
import config from "@lib/config" import config from "@lib/config"
import * as crypto from "crypto" import * as crypto from "crypto"
import { Provider } from "next-auth/providers"
const credentialsOptions = () => { const credentialsOptions = () => {
const options: Record<string, any> = { const options: Record<string, unknown> = {
username: { username: {
label: "Username", label: "Username",
required: true, required: true,
@ -47,7 +46,8 @@ const providers = () => {
providers.push( providers.push(
CredentialsProvider({ CredentialsProvider({
name: "Drift", name: "Drift",
credentials: credentialsOptions(), // @ts-expect-error TODO: fix types
credentials: credentialsOptions() as unknown,
async authorize(credentials) { async authorize(credentials) {
if (!credentials || !credentials.username || !credentials.password) { if (!credentials || !credentials.username || !credentials.password) {
throw new Error("Missing credentials") throw new Error("Missing credentials")

View file

@ -1,9 +1,10 @@
declare global { declare global {
// eslint-disable-next-line no-var
var prisma: PrismaClient | undefined var prisma: PrismaClient | undefined
} }
import config from "@lib/config" 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 type { User, File, Post } from "@prisma/client"
export const prisma = export const prisma =
@ -126,7 +127,7 @@ export async function getPostsByUser(userId: User["id"], withFiles?: boolean) {
select: { select: {
id: true, id: true,
title: true, title: true,
createdAt: true, createdAt: true
} }
} }
}) })
@ -136,7 +137,10 @@ export async function getPostsByUser(userId: User["id"], withFiles?: boolean) {
return posts 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({ const user = await prisma.user.findUnique({
where: { where: {
id: userId id: userId
@ -146,7 +150,8 @@ export const getUserById = async (userId: User["id"]) => {
email: true, email: true,
// displayName: true, // displayName: true,
role: true, role: true,
displayName: true displayName: true,
...selects
} }
}) })
@ -199,7 +204,7 @@ export const getPostById = async (
postId: Post["id"], postId: Post["id"],
options?: GetPostByIdOptions options?: GetPostByIdOptions
): Promise<Post | PostWithFiles | PostWithFilesAndAuthor | null> => { ): Promise<Post | PostWithFiles | PostWithFilesAndAuthor | null> => {
let post = await prisma.post.findUnique({ const post = await prisma.post.findUnique({
where: { where: {
id: postId id: postId
}, },

View file

@ -11,7 +11,7 @@ const epochs = [
] as const ] as const
const getDuration = (timeAgoInSeconds: number) => { const getDuration = (timeAgoInSeconds: number) => {
for (let [name, seconds] of epochs) { for (const [name, seconds] of epochs) {
const interval = Math.floor(timeAgoInSeconds / seconds) const interval = Math.floor(timeAgoInSeconds / seconds)
if (interval >= 1) { if (interval >= 1) {

View file

@ -6,7 +6,7 @@
"dev": "next dev --port 3000", "dev": "next dev --port 3000",
"build": "next build", "build": "next build",
"start": "next start --port 3000", "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", "analyze": "cross-env ANALYZE=true next build",
"find:unused": "next-unused", "find:unused": "next-unused",
"prisma": "prisma", "prisma": "prisma",
@ -51,6 +51,8 @@
"@types/react-datepicker": "4.4.1", "@types/react-datepicker": "4.4.1",
"@types/react-dom": "18.0.3", "@types/react-dom": "18.0.3",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"csstype": "^3.1.1", "csstype": "^3.1.1",

View file

@ -45,13 +45,15 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
switch (req.method) { switch (req.method) {
case "GET": case "GET":
switch (action) { switch (action) {
case "users": case "users": {
const users = await prisma.user.findMany() const users = await prisma.user.findMany()
return res.status(200).json(users) return res.status(200).json(users)
case "posts": }
case "posts": {
const posts = await prisma.post.findMany() const posts = await prisma.post.findMany()
return res.status(200).json(posts) return res.status(200).json(posts)
case "user": }
case "user": {
const { id: userId } = req.query const { id: userId } = req.query
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
@ -59,7 +61,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
}) })
return res.status(200).json(user) return res.status(200).json(user)
case "post": }
case "post": {
const { id: postId } = req.query const { id: postId } = req.query
const post = await prisma.post.findUnique({ const post = await prisma.post.findUnique({
where: { where: {
@ -67,11 +70,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
}) })
return res.status(200).json(post) return res.status(200).json(post)
}
} }
break break
case "PATCH": case "PATCH":
switch (action) { switch (action) {
case "set-role": case "set-role": {
const { userId, role } = req.body const { userId, role } = req.body
if (!userId || !role || role !== "admin" || role !== "user") { if (!userId || !role || role !== "admin" || role !== "user") {
return res.status(400).json({ error: "Invalid request" }) 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) return res.status(200).json(user)
}
} }
break break
case "DELETE": case "DELETE":
switch (action) { switch (action) {
case "delete-user": case "delete-user": {
const { userId } = req.body const { userId } = req.body
if (!userId) { if (!userId) {
return res.status(400).json({ error: "Invalid request" }) return res.status(400).json({ error: "Invalid request" })
@ -98,7 +103,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await deleteUser(userId) await deleteUser(userId)
return res.status(200).send("User deleted") return res.status(200).send("User deleted")
case "delete-post": }
case "delete-post": {
const { postId } = req.body const { postId } = req.body
if (!postId) { if (!postId) {
return res.status(400).json({ error: "Invalid request" }) 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) return res.status(200).json(post)
}
} }
break break
} }

View file

@ -28,6 +28,8 @@ export default async function requiresPasscode(
if (slug === "requires-passcode") { if (slug === "requires-passcode") {
return handleRequiresPasscode(req, res) return handleRequiresPasscode(req, res)
} }
return res.status(404).json({ error: "Not found" })
default: default:
return res.status(405).json({ error: "Method not allowed" }) return res.status(405).json({ error: "Method not allowed" })
} }

View file

@ -1,5 +1,5 @@
import { NextApiRequest, NextApiResponse } from "next" 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" import { parseQueryParam } from "@lib/server/parse-query-param"
const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => {

View file

@ -1,9 +1,9 @@
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
const handler = async (_: NextApiRequest, res: NextApiResponse) => { const handler = async (_: NextApiRequest, res: NextApiResponse) => {
return res.json({ return res.json({
status: "UP" status: "UP"
}) })
} }
export default handler export default handler

View file

@ -1,6 +1,6 @@
import { withMethods } from "@lib/api-middleware/with-methods" import { withMethods } from "@lib/api-middleware/with-methods"
import { parseQueryParam } from "@lib/server/parse-query-param" 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 type { NextApiRequest, NextApiResponse } from "next"
import { getSession } from "next-auth/react" import { getSession } from "next-auth/react"
import { prisma } from "lib/server/prisma" import { prisma } from "lib/server/prisma"
@ -14,7 +14,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
export default withMethods(["GET", "PUT", "DELETE"], handler) 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) const id = parseQueryParam(req.query.id)
if (!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 // the user can always go directly to their own post
if (session?.user.id === post.authorId) { if (session?.user.id === post.authorId) {
return res.json({ return res.json({
...post, ...post
}) })
} }
@ -58,7 +58,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse<any>) {
if (hash === post.password) { if (hash === post.password) {
return res.json({ return res.json({
...post, ...post
}) })
} else { } else {
return { return {
@ -76,7 +76,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse<any>) {
} }
// PUT is for adjusting visibility and password // 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 { password, visibility } = req.body
const id = parseQueryParam(req.query.id) 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) const id = parseQueryParam(req.query.id)
if (!id) { if (!id) {

View file

@ -1,16 +1,12 @@
// nextjs typescript api handler
import { withMethods } from "@lib/api-middleware/with-methods" import { withMethods } from "@lib/api-middleware/with-methods"
import { authOptions } from "@lib/server/auth" 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 { NextApiRequest, NextApiResponse } from "next"
import { unstable_getServerSession } from "next-auth/next" import { unstable_getServerSession } from "next-auth/next"
import { File } from "@lib/server/prisma" import { File } from "@lib/server/prisma"
import * as crypto from "crypto" import * as crypto from "crypto"
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file" 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) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return await handlePost(req, res) return await handlePost(req, res)
@ -18,7 +14,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
export default withMethods(["POST"], handler) export default withMethods(["POST"], handler)
async function handlePost(req: NextApiRequest, res: NextApiResponse<any>) { async function handlePost(req: NextApiRequest, res: NextApiResponse<unknown>) {
try { try {
const session = await unstable_getServerSession(req, res, authOptions) const session = await unstable_getServerSession(req, res, authOptions)
if (!session || !session.user.id) { 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") throw new Error("You must submit at least one file")
} }
let hashedPassword: string = "" let hashedPassword = ""
if (req.body.visibility === "protected") { if (req.body.visibility === "protected") {
hashedPassword = crypto hashedPassword = crypto
.createHash("sha256") .createHash("sha256")

View file

@ -20,7 +20,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
let posts: ServerPostWithFiles[] let posts: ServerPostWithFiles[]
if (session?.user.id === user || session?.user.role === "admin") { if (session?.user.id === user || session?.user.role === "admin") {
posts = await searchPosts(query, { posts = await searchPosts(query, {
userId: user, userId: user
}) })
} else { } else {
posts = await searchPosts(query, { posts = await searchPosts(query, {

View file

@ -23,7 +23,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
switch (req.method) { switch (req.method) {
case "PUT": case "PUT": {
const { displayName } = req.body const { displayName } = req.body
const updatedUser = await prisma.user.update({ const updatedUser = await prisma.user.update({
where: { where: {
@ -40,6 +40,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
name: updatedUser.displayName name: updatedUser.displayName
// bio: updatedUser.bio // bio: updatedUser.bio
}) })
}
case "GET": case "GET":
return res.json(currUser) return res.json(currUser)
case "DELETE": case "DELETE":
@ -48,7 +49,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
await deleteUser(id) await deleteUser(id)
break
default: default:
return res.status(405).json({ message: "Method not allowed" }) 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 * @warning This function does not perform any authorization checks
*/ */
export async function deleteUser(id: string | undefined) { export async function deleteUser(id: string | undefined) {
// first delete all of the user's posts // first delete all of the user's posts
await prisma.post.deleteMany({ await prisma.post.deleteMany({
where: { where: {

View file

@ -7,7 +7,7 @@ export default async function handle(
res: NextApiResponse res: NextApiResponse
) { ) {
switch (req.method) { switch (req.method) {
case "GET": case "GET": {
const userId = parseQueryParam(req.query.userId) const userId = parseQueryParam(req.query.userId)
if (!userId) { if (!userId) {
return res.status(400).json({ error: "Missing userId" }) return res.status(400).json({ error: "Missing userId" })
@ -15,6 +15,7 @@ export default async function handle(
const posts = await getPostsByUser(userId) const posts = await getPostsByUser(userId)
return res.json(posts) return res.json(posts)
}
default: default:
return res.status(405).end() return res.status(405).end()
} }

View file

@ -18,6 +18,8 @@ specifiers:
'@types/react-datepicker': 4.4.1 '@types/react-datepicker': 4.4.1
'@types/react-dom': 18.0.3 '@types/react-dom': 18.0.3
'@types/uuid': ^9.0.0 '@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 '@wcj/markdown-to-html': ^2.1.2
'@wits/next-themes': 0.2.14 '@wits/next-themes': 0.2.14
client-only: ^0.0.1 client-only: ^0.0.1
@ -90,6 +92,8 @@ devDependencies:
'@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y '@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y
'@types/react-dom': 18.0.3 '@types/react-dom': 18.0.3
'@types/uuid': 9.0.0 '@types/uuid': 9.0.0
'@typescript-eslint/eslint-plugin': 5.46.1_byqm7zzsgtndvuamqqta6vngru
'@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i
clsx: 1.2.1 clsx: 1.2.1
cross-env: 7.0.3 cross-env: 7.0.3
csstype: 3.1.1 csstype: 3.1.1
@ -1509,6 +1513,10 @@ packages:
'@types/istanbul-lib-report': 3.0.0 '@types/istanbul-lib-report': 3.0.0
dev: false dev: false
/@types/json-schema/7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
/@types/json5/0.0.29: /@types/json5/0.0.29:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true dev: true
@ -1583,6 +1591,10 @@ packages:
/@types/scheduler/0.16.2: /@types/scheduler/0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} 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: /@types/stack-utils/2.0.1:
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
dev: false dev: false
@ -1605,8 +1617,35 @@ packages:
'@types/yargs-parser': 21.0.0 '@types/yargs-parser': 21.0.0
dev: false dev: false
/@typescript-eslint/parser/5.42.1_hsmo2rtalirsvadpuxki35bq2i: /@typescript-eslint/eslint-plugin/5.46.1_byqm7zzsgtndvuamqqta6vngru:
resolution: {integrity: sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==} 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} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@ -1615,9 +1654,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 5.42.1 '@typescript-eslint/scope-manager': 5.46.1
'@typescript-eslint/types': 5.42.1 '@typescript-eslint/types': 5.46.1
'@typescript-eslint/typescript-estree': 5.42.1_typescript@4.6.4 '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.6.4
debug: 4.3.4 debug: 4.3.4
eslint: 8.27.0 eslint: 8.27.0
typescript: 4.6.4 typescript: 4.6.4
@ -1625,12 +1664,32 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager/5.42.1: /@typescript-eslint/scope-manager/5.46.1:
resolution: {integrity: sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ==} resolution: {integrity: sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.42.1 '@typescript-eslint/types': 5.46.1
'@typescript-eslint/visitor-keys': 5.42.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 dev: true
/@typescript-eslint/types/4.33.0: /@typescript-eslint/types/4.33.0:
@ -1638,8 +1697,8 @@ packages:
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
dev: true dev: true
/@typescript-eslint/types/5.42.1: /@typescript-eslint/types/5.46.1:
resolution: {integrity: sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA==} resolution: {integrity: sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
@ -1664,8 +1723,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/typescript-estree/5.42.1_typescript@4.6.4: /@typescript-eslint/typescript-estree/5.46.1_typescript@4.6.4:
resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==} resolution: {integrity: sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -1673,8 +1732,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 5.42.1 '@typescript-eslint/types': 5.46.1
'@typescript-eslint/visitor-keys': 5.42.1 '@typescript-eslint/visitor-keys': 5.46.1
debug: 4.3.4 debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
@ -1685,6 +1744,26 @@ packages:
- supports-color - supports-color
dev: true 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: /@typescript-eslint/visitor-keys/4.33.0:
resolution: {integrity: sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==} resolution: {integrity: sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==}
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
@ -1693,11 +1772,11 @@ packages:
eslint-visitor-keys: 2.1.0 eslint-visitor-keys: 2.1.0
dev: true dev: true
/@typescript-eslint/visitor-keys/5.42.1: /@typescript-eslint/visitor-keys/5.46.1:
resolution: {integrity: sha512-LOQtSF4z+hejmpUvitPlc4hA7ERGoj2BVkesOcG91HCn8edLGUXbTrErmutmPbl8Bo9HjAvOO/zBKQHExXNA2A==} resolution: {integrity: sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.42.1 '@typescript-eslint/types': 5.46.1
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
@ -2780,11 +2859,11 @@ packages:
dependencies: dependencies:
'@next/eslint-plugin-next': 13.0.3 '@next/eslint-plugin-next': 13.0.3
'@rushstack/eslint-patch': 1.2.0 '@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: 8.27.0
eslint-import-resolver-node: 0.3.6 eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 2.7.1_dcpv4nbdr5ks2h5677xdltrk6e 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-jsx-a11y: 6.6.1_eslint@8.27.0
eslint-plugin-react: 7.31.10_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 eslint-plugin-react-hooks: 4.6.0_eslint@8.27.0
@ -2812,7 +2891,7 @@ packages:
dependencies: dependencies:
debug: 4.3.4 debug: 4.3.4
eslint: 8.27.0 eslint: 8.27.0
eslint-plugin-import: 2.26.0_fjrawv2a4e2kreqduevmayjdry eslint-plugin-import: 2.26.0_gqysc5ehwyt3mg2jls3nr3332q
glob: 7.2.3 glob: 7.2.3
is-glob: 4.0.3 is-glob: 4.0.3
resolve: 1.22.1 resolve: 1.22.1
@ -2821,7 +2900,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-module-utils/2.7.4_c5vbubjxm3sqe7zyydgtitlaga: /eslint-module-utils/2.7.4_nw56cc7ve4sv3zakvubomgge2q:
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
@ -2842,7 +2921,7 @@ packages:
eslint-import-resolver-webpack: eslint-import-resolver-webpack:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.42.1_hsmo2rtalirsvadpuxki35bq2i '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i
debug: 3.2.7 debug: 3.2.7
eslint: 8.27.0 eslint: 8.27.0
eslint-import-resolver-node: 0.3.6 eslint-import-resolver-node: 0.3.6
@ -2851,7 +2930,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-import/2.26.0_fjrawv2a4e2kreqduevmayjdry: /eslint-plugin-import/2.26.0_gqysc5ehwyt3mg2jls3nr3332q:
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
@ -2861,14 +2940,14 @@ packages:
'@typescript-eslint/parser': '@typescript-eslint/parser':
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.42.1_hsmo2rtalirsvadpuxki35bq2i '@typescript-eslint/parser': 5.46.1_hsmo2rtalirsvadpuxki35bq2i
array-includes: 3.1.6 array-includes: 3.1.6
array.prototype.flat: 1.3.1 array.prototype.flat: 1.3.1
debug: 2.6.9 debug: 2.6.9
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.27.0 eslint: 8.27.0
eslint-import-resolver-node: 0.3.6 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 has: 1.0.3
is-core-module: 2.11.0 is-core-module: 2.11.0
is-glob: 4.0.3 is-glob: 4.0.3
@ -2936,6 +3015,14 @@ packages:
string.prototype.matchall: 4.0.8 string.prototype.matchall: 4.0.8
dev: true 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: /eslint-scope/7.1.1:
resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -3040,6 +3127,11 @@ packages:
estraverse: 5.3.0 estraverse: 5.3.0
dev: true dev: true
/estraverse/4.3.0:
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
engines: {node: '>=4.0'}
dev: true
/estraverse/5.3.0: /estraverse/5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -5102,6 +5194,10 @@ packages:
dev: false dev: false
optional: true optional: true
/natural-compare-lite/1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
dev: true
/natural-compare/1.4.0: /natural-compare/1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}