use next-auth, add sign in via github, switch to postgres
This commit is contained in:
parent
7c761eb727
commit
60d1b031f5
56 changed files with 824 additions and 710 deletions
|
@ -1,5 +1,11 @@
|
||||||
import Auth from "@components/auth"
|
import Auth from "@components/auth"
|
||||||
|
import Header from "@components/header"
|
||||||
|
|
||||||
export default function SignInPage() {
|
export default function SignInPage() {
|
||||||
return <Auth page="signin" />
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<Auth page="signin" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Auth from "@components/auth"
|
import Auth from "@components/auth"
|
||||||
|
import Header from "@components/header"
|
||||||
import { getRequiresPasscode } from "pages/api/auth/requires-passcode"
|
import { getRequiresPasscode } from "pages/api/auth/requires-passcode"
|
||||||
|
|
||||||
const getPasscode = async () => {
|
const getPasscode = async () => {
|
||||||
|
@ -7,5 +8,10 @@ const getPasscode = async () => {
|
||||||
|
|
||||||
export default async function SignUpPage() {
|
export default async function SignUpPage() {
|
||||||
const requiresPasscode = await getPasscode()
|
const requiresPasscode = await getPasscode()
|
||||||
return <Auth page="signup" requiresServerPassword={requiresPasscode} />
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<Auth page="signup" requiresServerPassword={requiresPasscode} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ const Home = ({
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
alt=""
|
alt=""
|
||||||
|
priority
|
||||||
/>
|
/>
|
||||||
</ShiftBy>
|
</ShiftBy>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import Header from "@components/header"
|
||||||
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { getWelcomeContent } from "pages/api/welcome"
|
import { getWelcomeContent } from "pages/api/welcome"
|
||||||
import Home from "./home"
|
import Home from "./home"
|
||||||
|
|
||||||
|
@ -8,6 +10,12 @@ const getWelcomeData = async () => {
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { content, rendered, title } = await getWelcomeData()
|
const { content, rendered, title } = await getWelcomeData()
|
||||||
|
const authed = await getCurrentUser();
|
||||||
return <Home rendered={rendered} introContent={content} introTitle={title} />
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header signedIn={Boolean(authed)}/>
|
||||||
|
<Home rendered={rendered} introContent={content} introTitle={title} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import NewPost from "@components/new-post"
|
import NewPost from "@components/new-post"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { cookies } from "next/headers"
|
import { getPostWithFiles } from "@lib/server/prisma"
|
||||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
import Header from "@components/header"
|
||||||
import { getPostWithFiles } from "app/prisma"
|
|
||||||
import { useRedirectIfNotAuthed } from "@lib/server/hooks/use-redirect-if-not-authed"
|
|
||||||
|
|
||||||
const NewFromExisting = async ({
|
const NewFromExisting = async ({
|
||||||
params
|
params
|
||||||
|
@ -14,13 +12,6 @@ const NewFromExisting = async ({
|
||||||
}) => {
|
}) => {
|
||||||
const { id } = params
|
const { id } = params
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const cookieList = cookies()
|
|
||||||
useRedirectIfNotAuthed()
|
|
||||||
const driftToken = cookieList.get(TOKEN_COOKIE_NAME)
|
|
||||||
|
|
||||||
if (!driftToken) {
|
|
||||||
return router.push("/signin")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return router.push("/new")
|
return router.push("/new")
|
||||||
|
@ -28,7 +19,12 @@ const NewFromExisting = async ({
|
||||||
|
|
||||||
const post = await getPostWithFiles(id)
|
const post = await getPostWithFiles(id)
|
||||||
|
|
||||||
return <NewPost initialPost={post} newPostParent={id} />
|
return (
|
||||||
|
<>
|
||||||
|
<Header signedIn />
|
||||||
|
<NewPost initialPost={post} newPostParent={id} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewFromExisting
|
export default NewFromExisting
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
|
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 }) {
|
||||||
// useRedirectIfNotAuthed()
|
const user = getCurrentUser()
|
||||||
|
if (!user) {
|
||||||
|
return redirect("/new")
|
||||||
|
}
|
||||||
|
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import Header from "@components/header"
|
||||||
import NewPost from "@components/new-post"
|
import NewPost from "@components/new-post"
|
||||||
import "@styles/react-datepicker.css"
|
import "@styles/react-datepicker.css"
|
||||||
|
|
||||||
const New = () => <NewPost />
|
const New = () => <>
|
||||||
|
<Header signedIn />
|
||||||
|
<NewPost />
|
||||||
|
</>
|
||||||
|
|
||||||
export default New
|
export default New
|
||||||
|
|
24
client/app/(posts)/post/[id]/head.tsx
Normal file
24
client/app/(posts)/post/[id]/head.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import PageSeo from "@components/page-seo"
|
||||||
|
import { getPostById } from "@lib/server/prisma"
|
||||||
|
|
||||||
|
export default async function Head({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: {
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const post = await getPostById(params.slug)
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSeo
|
||||||
|
title={`${post.title} - Drift`}
|
||||||
|
description={post.description}
|
||||||
|
isPrivate={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,16 +1,26 @@
|
||||||
import { USER_COOKIE_NAME } from "@lib/constants"
|
import { USER_COOKIE_NAME } from "@lib/constants"
|
||||||
import { notFound, useRouter } from "next/navigation"
|
import { notFound, redirect, useRouter } from "next/navigation"
|
||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
import { getPostsByUser } from "app/prisma"
|
import { getPostsByUser } from "@lib/server/prisma"
|
||||||
import PostList from "@components/post-list"
|
import PostList from "@components/post-list"
|
||||||
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
|
import Header from "@components/header"
|
||||||
|
import { authOptions } from "@lib/server/auth"
|
||||||
|
|
||||||
export default async function Mine() {
|
export default async function Mine() {
|
||||||
const userId = cookies().get(USER_COOKIE_NAME)?.value
|
const userId = (await getCurrentUser())?.id
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return notFound()
|
redirect(authOptions.pages?.signIn || "/new")
|
||||||
}
|
}
|
||||||
|
|
||||||
const posts = await getPostsByUser(userId, true)
|
const posts = await getPostsByUser(userId, true)
|
||||||
|
|
||||||
const hasMore = false
|
const hasMore = false
|
||||||
return <PostList morePosts={hasMore} initialPosts={posts} />
|
return (
|
||||||
|
<>
|
||||||
|
<Header signedIn />
|
||||||
|
<PostList morePosts={hasMore} initialPosts={posts} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,37 @@
|
||||||
import SettingsPage from "@components/settings"
|
import Header from "@components/header"
|
||||||
|
import SettingsGroup from "@components/settings-group"
|
||||||
|
import Password from "@components/settings/sections/password"
|
||||||
|
import Profile from "@components/settings/sections/profile"
|
||||||
|
import { authOptions } from "@lib/server/auth"
|
||||||
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
const Settings = () => <SettingsPage />
|
export default async function SettingsPage() {
|
||||||
|
const user = await getCurrentUser()
|
||||||
|
|
||||||
export default Settings
|
if (!user) {
|
||||||
|
redirect(authOptions.pages?.signIn || "/new")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header signedIn />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "var(--gap)",
|
||||||
|
marginBottom: "var(--gap)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<SettingsGroup title="Profile">
|
||||||
|
<Profile user={user} />
|
||||||
|
</SettingsGroup>
|
||||||
|
<SettingsGroup title="Password">
|
||||||
|
<Password />
|
||||||
|
</SettingsGroup>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import Admin from "@components/admin"
|
import Admin from "@components/admin"
|
||||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||||
import { isUserAdmin } from "app/prisma"
|
import { isUserAdmin } from "@lib/server/prisma"
|
||||||
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
const AdminPage = async () => {
|
const AdminPage = async () => {
|
||||||
const driftToken = cookies().get(TOKEN_COOKIE_NAME)?.value
|
const user = await getCurrentUser()
|
||||||
if (!driftToken) {
|
|
||||||
|
if (!user) {
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdmin = await isUserAdmin(driftToken)
|
if (user.role !== "admin") {
|
||||||
|
|
||||||
if (!isAdmin) {
|
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import "@styles/globals.css"
|
import "@styles/globals.css"
|
||||||
import { ServerThemeProvider } from "next-themes"
|
import { ServerThemeProvider } from "next-themes"
|
||||||
import { LayoutWrapper } from "./root-layout-wrapper"
|
import { LayoutWrapper } from "./root-layout-wrapper"
|
||||||
|
import styles from '@styles/Home.module.css';
|
||||||
|
|
||||||
interface RootLayoutProps {
|
interface RootLayoutProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({ children }: RootLayoutProps) {
|
export default async function RootLayout({ children }: RootLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<ServerThemeProvider
|
<ServerThemeProvider
|
||||||
cookieName="drift-theme"
|
cookieName="drift-theme"
|
||||||
// disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
attribute="data-theme"
|
attribute="data-theme"
|
||||||
enableColorScheme
|
enableColorScheme
|
||||||
>
|
>
|
||||||
|
@ -50,7 +51,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<title>Drift</title>
|
<title>Drift</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body className={styles.main}>
|
||||||
<LayoutWrapper>{children}</LayoutWrapper>
|
<LayoutWrapper>{children}</LayoutWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
import { Post, PrismaClient, File, User } from "@prisma/client"
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
export default prisma
|
|
||||||
|
|
||||||
export type { User, AuthTokens, File, Post } from "@prisma/client"
|
|
||||||
|
|
||||||
export type PostWithFiles = Post & {
|
|
||||||
files: File[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFilesForPost = async (postId: string) => {
|
|
||||||
const files = await prisma.file.findMany({
|
|
||||||
where: {
|
|
||||||
postId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPostWithFiles = async (
|
|
||||||
postId: string
|
|
||||||
): Promise<PostWithFiles | undefined> => {
|
|
||||||
const post = await prisma.post.findUnique({
|
|
||||||
where: {
|
|
||||||
id: postId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!post) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = await getFilesForPost(postId)
|
|
||||||
|
|
||||||
if (!files) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...post,
|
|
||||||
files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPostsByUser(userId: string): Promise<Post[]>
|
|
||||||
export async function getPostsByUser(
|
|
||||||
userId: string,
|
|
||||||
includeFiles: true
|
|
||||||
): Promise<PostWithFiles[]>
|
|
||||||
export async function getPostsByUser(userId: User["id"], withFiles?: boolean) {
|
|
||||||
const sharedOptions = {
|
|
||||||
take: 20,
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc" as const
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const posts = await prisma.post.findMany({
|
|
||||||
where: {
|
|
||||||
authorId: userId
|
|
||||||
},
|
|
||||||
...sharedOptions
|
|
||||||
})
|
|
||||||
|
|
||||||
if (withFiles) {
|
|
||||||
return Promise.all(
|
|
||||||
posts.map(async (post) => {
|
|
||||||
const files = await prisma.file.findMany({
|
|
||||||
where: {
|
|
||||||
postId: post.id
|
|
||||||
},
|
|
||||||
...sharedOptions
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
...post,
|
|
||||||
files
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return posts
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getUserById = async (userId: User["id"]) => {
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
id: userId
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
email: true,
|
|
||||||
displayName: true,
|
|
||||||
role: true,
|
|
||||||
username: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isUserAdmin = async (userId: User["id"]) => {
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
id: userId
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
role: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return user?.role?.toLowerCase() === "admin"
|
|
||||||
}
|
|
|
@ -1,12 +1,14 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import Header from "@components/header"
|
|
||||||
import { CssBaseline, GeistProvider, Page, Themes } from "@geist-ui/core/dist"
|
import { CssBaseline, GeistProvider, Page, Themes } from "@geist-ui/core/dist"
|
||||||
import { ThemeProvider } from "next-themes"
|
import { ThemeProvider } from "next-themes"
|
||||||
import { SkeletonTheme } from "react-loading-skeleton"
|
import { SkeletonTheme } from "react-loading-skeleton"
|
||||||
import styles from "@styles/Home.module.css"
|
|
||||||
|
|
||||||
export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
export function LayoutWrapper({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
const skeletonBaseColor = "var(--light-gray)"
|
const skeletonBaseColor = "var(--light-gray)"
|
||||||
const skeletonHighlightColor = "var(--lighter-gray)"
|
const skeletonHighlightColor = "var(--lighter-gray)"
|
||||||
|
|
||||||
|
@ -30,7 +32,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||||
dropdownBoxShadow: "0 0 0 1px var(--lighter-gray)",
|
dropdownBoxShadow: "0 0 0 1px var(--lighter-gray)",
|
||||||
shadowSmall: "0 0 0 1px var(--lighter-gray)",
|
shadowSmall: "0 0 0 1px var(--lighter-gray)",
|
||||||
shadowLarge: "0 0 0 1px var(--lighter-gray)",
|
shadowLarge: "0 0 0 1px var(--lighter-gray)",
|
||||||
shadowMedium: "0 0 0 1px var(--lighter-gray)",
|
shadowMedium: "0 0 0 1px var(--lighter-gray)"
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
gap: "var(--gap)",
|
gap: "var(--gap)",
|
||||||
|
@ -59,12 +61,11 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||||
attribute="data-theme"
|
attribute="data-theme"
|
||||||
>
|
>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Page width={"100%"}>
|
<Page width={"100%"} style={{
|
||||||
<Page.Header>
|
marginTop: "0 !important",
|
||||||
<Header />
|
paddingTop: "0 !important"
|
||||||
</Page.Header>
|
}}>
|
||||||
|
{children}
|
||||||
<Page.Content className={styles.main}>{children}</Page.Content>
|
|
||||||
</Page>
|
</Page>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SkeletonTheme>
|
</SkeletonTheme>
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
max-width: 300px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.formContentSpace {
|
.formContentSpace {
|
||||||
|
|
|
@ -4,11 +4,9 @@ import { FormEvent, useState } from "react"
|
||||||
import styles from "./auth.module.css"
|
import styles from "./auth.module.css"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import Link from "../link"
|
import Link from "../link"
|
||||||
import useSignedIn from "@lib/hooks/use-signed-in"
|
|
||||||
import { USER_COOKIE_NAME } from "@lib/constants"
|
|
||||||
import { setCookie } from "cookies-next"
|
|
||||||
import { Button, Input, Note } from "@geist-ui/core/dist"
|
import { Button, Input, Note } from "@geist-ui/core/dist"
|
||||||
|
import { signIn } from "next-auth/react"
|
||||||
|
import { Github as GithubIcon } from "@geist-ui/icons"
|
||||||
const NO_EMPTY_SPACE_REGEX = /^\S*$/
|
const NO_EMPTY_SPACE_REGEX = /^\S*$/
|
||||||
const ERROR_MESSAGE =
|
const ERROR_MESSAGE =
|
||||||
"Provide a non empty username and a password with at least 6 characters"
|
"Provide a non empty username and a password with at least 6 characters"
|
||||||
|
@ -27,29 +25,27 @@ const Auth = ({
|
||||||
const [serverPassword, setServerPassword] = useState("")
|
const [serverPassword, setServerPassword] = useState("")
|
||||||
const [errorMsg, setErrorMsg] = useState("")
|
const [errorMsg, setErrorMsg] = useState("")
|
||||||
const signingIn = page === "signin"
|
const signingIn = page === "signin"
|
||||||
const { signin } = useSignedIn()
|
|
||||||
|
|
||||||
const handleJson = (json: any) => {
|
const handleJson = (json: any) => {
|
||||||
signin(json.token)
|
// setCookie(USER_COOKIE_NAME, json.userId)
|
||||||
setCookie(USER_COOKIE_NAME, json.userId)
|
|
||||||
|
|
||||||
router.push("/new")
|
router.push("/new")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (
|
// if (
|
||||||
!signingIn &&
|
// !signingIn &&
|
||||||
(!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)
|
// (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)
|
||||||
)
|
// )
|
||||||
return setErrorMsg(ERROR_MESSAGE)
|
// return setErrorMsg(ERROR_MESSAGE)
|
||||||
if (
|
// if (
|
||||||
!signingIn &&
|
// !signingIn &&
|
||||||
requiresServerPassword &&
|
// requiresServerPassword &&
|
||||||
!NO_EMPTY_SPACE_REGEX.test(serverPassword)
|
// !NO_EMPTY_SPACE_REGEX.test(serverPassword)
|
||||||
)
|
// )
|
||||||
return setErrorMsg(ERROR_MESSAGE)
|
// return setErrorMsg(ERROR_MESSAGE)
|
||||||
else setErrorMsg("")
|
// else setErrorMsg("")
|
||||||
|
|
||||||
const reqOpts = {
|
const reqOpts = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -60,12 +56,18 @@ const Auth = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const signUrl = signingIn ? "/api/auth/signin" : "/api/auth/signup"
|
// signIn("credentials", {
|
||||||
const resp = await fetch(signUrl, reqOpts)
|
// callbackUrl: "/new",
|
||||||
const json = await resp.json()
|
// redirect: false,
|
||||||
if (!resp.ok) throw new Error(json.error.message)
|
// username,
|
||||||
|
// password,
|
||||||
handleJson(json)
|
// serverPassword
|
||||||
|
// })
|
||||||
|
// const signUrl = signingIn ? "/api/auth/signin" : "/api/auth/signup"
|
||||||
|
// const resp = await fetch(signUrl, reqOpts)
|
||||||
|
// const json = await resp.json()
|
||||||
|
// if (!resp.ok) throw new Error(json.error.message)
|
||||||
|
// handleJson(json)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setErrorMsg(err.message ?? "Something went wrong")
|
setErrorMsg(err.message ?? "Something went wrong")
|
||||||
}
|
}
|
||||||
|
@ -77,9 +79,10 @@ const Auth = ({
|
||||||
<div className={styles.formContentSpace}>
|
<div className={styles.formContentSpace}>
|
||||||
<h1>{signingIn ? "Sign In" : "Sign Up"}</h1>
|
<h1>{signingIn ? "Sign In" : "Sign Up"}</h1>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit}>
|
{/* <form onSubmit={handleSubmit}> */}
|
||||||
|
<form>
|
||||||
<div className={styles.formGroup}>
|
<div className={styles.formGroup}>
|
||||||
<Input
|
{/* <Input
|
||||||
htmlType="text"
|
htmlType="text"
|
||||||
id="username"
|
id="username"
|
||||||
value={username}
|
value={username}
|
||||||
|
@ -87,6 +90,7 @@ const Auth = ({
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
required
|
required
|
||||||
minLength={3}
|
minLength={3}
|
||||||
|
width="100%"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
htmlType="password"
|
htmlType="password"
|
||||||
|
@ -96,7 +100,9 @@ const Auth = ({
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
required
|
required
|
||||||
minLength={6}
|
minLength={6}
|
||||||
/>
|
width="100%"
|
||||||
|
/> */}
|
||||||
|
{/* sign in with github */}
|
||||||
{requiresServerPassword && (
|
{requiresServerPassword && (
|
||||||
<Input
|
<Input
|
||||||
htmlType="password"
|
htmlType="password"
|
||||||
|
@ -110,10 +116,20 @@ const Auth = ({
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
<Button width={"100%"} htmlType="submit">
|
htmlType="submit"
|
||||||
{signingIn ? "Sign In" : "Sign Up"}
|
type="success-light"
|
||||||
|
auto
|
||||||
|
width="100%"
|
||||||
|
icon={<GithubIcon />}
|
||||||
|
onClick={() => signIn("github")}
|
||||||
|
>
|
||||||
|
Sign in with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* <Button width={"100%"} htmlType="submit">
|
||||||
|
{signingIn ? "Sign In" : "Sign Up"}
|
||||||
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.formContentSpace}>
|
<div className={styles.formContentSpace}>
|
||||||
{signingIn ? (
|
{signingIn ? (
|
||||||
|
@ -125,7 +141,7 @@ const Auth = ({
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p>
|
<p>
|
||||||
Already have an account?{" "}
|
Have an account?{" "}
|
||||||
<Link colored href="/signin">
|
<Link colored href="/signin">
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
.mobile {
|
.mobile {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import styles from "./header.module.css"
|
import styles from "./header.module.css"
|
||||||
import useSignedIn from "../../lib/hooks/use-signed-in"
|
|
||||||
|
|
||||||
import HomeIcon from "@geist-ui/icons/home"
|
import HomeIcon from "@geist-ui/icons/home"
|
||||||
import MenuIcon from "@geist-ui/icons/menu"
|
import MenuIcon from "@geist-ui/icons/menu"
|
||||||
|
@ -25,7 +24,7 @@ import MoonIcon from "@geist-ui/icons/moon"
|
||||||
import SettingsIcon from "@geist-ui/icons/settings"
|
import SettingsIcon from "@geist-ui/icons/settings"
|
||||||
import SunIcon from "@geist-ui/icons/sun"
|
import SunIcon from "@geist-ui/icons/sun"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import useUserData from "@lib/hooks/use-user-data"
|
// import useUserData from "@lib/hooks/use-user-data"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
|
@ -37,13 +36,13 @@ type Tab = {
|
||||||
href?: string
|
href?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = () => {
|
const Header = ({ signedIn = false }) => {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const [expanded, setExpanded] = useState<boolean>(false)
|
const [expanded, setExpanded] = useState<boolean>(false)
|
||||||
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
|
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
|
||||||
const isMobile = useMediaQuery("xs", { match: "down" })
|
const isMobile = useMediaQuery("xs", { match: "down" })
|
||||||
const { signedIn: isSignedIn } = useSignedIn()
|
// const { status } = useSession()
|
||||||
const userData = useUserData()
|
// const signedIn = status === "authenticated"
|
||||||
const [pages, setPages] = useState<Tab[]>([])
|
const [pages, setPages] = useState<Tab[]>([])
|
||||||
const { setTheme, resolvedTheme } = useTheme()
|
const { setTheme, resolvedTheme } = useTheme()
|
||||||
|
|
||||||
|
@ -76,7 +75,7 @@ const Header = () => {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (isSignedIn)
|
if (signedIn)
|
||||||
setPages([
|
setPages([
|
||||||
{
|
{
|
||||||
name: "new",
|
name: "new",
|
||||||
|
@ -126,20 +125,20 @@ const Header = () => {
|
||||||
},
|
},
|
||||||
...defaultPages
|
...defaultPages
|
||||||
])
|
])
|
||||||
if (userData?.role === "admin") {
|
// if (userData?.role === "admin") {
|
||||||
setPages((pages) => [
|
// setPages((pages) => [
|
||||||
...pages,
|
// ...pages,
|
||||||
{
|
// {
|
||||||
name: "admin",
|
// name: "admin",
|
||||||
icon: <SettingsIcon />,
|
// icon: <SettingsIcon />,
|
||||||
value: "admin",
|
// value: "admin",
|
||||||
href: "/admin"
|
// href: "/admin"
|
||||||
}
|
// }
|
||||||
])
|
// ])
|
||||||
}
|
// }
|
||||||
// TODO: investigate deps causing infinite loop
|
// TODO: investigate deps causing infinite loop
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isMobile, isSignedIn, resolvedTheme, userData])
|
}, [isMobile, resolvedTheme])
|
||||||
|
|
||||||
const onTabChange = useCallback(
|
const onTabChange = useCallback(
|
||||||
(tab: string) => {
|
(tab: string) => {
|
||||||
|
@ -172,7 +171,6 @@ const Header = () => {
|
||||||
return (
|
return (
|
||||||
<Link key={tab.value} href={tab.href} className={styles.tab}>
|
<Link key={tab.value} href={tab.href} className={styles.tab}>
|
||||||
<Button
|
<Button
|
||||||
className={activeStyle}
|
|
||||||
auto={isMobile ? false : true}
|
auto={isMobile ? false : true}
|
||||||
icon={tab.icon}
|
icon={tab.icon}
|
||||||
shadow={false}
|
shadow={false}
|
||||||
|
@ -222,4 +220,4 @@ const Header = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Header
|
export default Header
|
|
@ -14,7 +14,7 @@ import { ChangeEvent } from "react"
|
||||||
import DatePicker from "react-datepicker"
|
import DatePicker from "react-datepicker"
|
||||||
import getTitleForPostCopy from "@lib/get-title-for-post-copy"
|
import getTitleForPostCopy from "@lib/get-title-for-post-copy"
|
||||||
import Description from "./description"
|
import Description from "./description"
|
||||||
import { PostWithFiles } from "app/prisma"
|
import { PostWithFiles } from "@lib/server/prisma"
|
||||||
import { TOKEN_COOKIE_NAME, USER_COOKIE_NAME } from "@lib/constants"
|
import { TOKEN_COOKIE_NAME, USER_COOKIE_NAME } from "@lib/constants"
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from "cookies-next"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from "react"
|
||||||
|
|
||||||
type PageSeoProps = {
|
type PageSeoProps = {
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string | null
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
isPrivate?: boolean
|
isPrivate?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import useDebounce from "@lib/hooks/use-debounce"
|
||||||
import Link from "@components/link"
|
import Link from "@components/link"
|
||||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from "cookies-next"
|
||||||
import type { PostWithFiles } from "app/prisma"
|
import type { PostWithFiles } from "@lib/server/prisma"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialPosts: PostWithFiles[]
|
initialPosts: PostWithFiles[]
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { useRouter } from "next/router"
|
||||||
import Parent from "@geist-ui/icons/arrowUpCircle"
|
import Parent from "@geist-ui/icons/arrowUpCircle"
|
||||||
import styles from "./list-item.module.css"
|
import styles from "./list-item.module.css"
|
||||||
import Link from "@components/link"
|
import Link from "@components/link"
|
||||||
import { PostWithFiles, File } from "app/prisma"
|
import { PostWithFiles, File } from "@lib/server/prisma"
|
||||||
import { PostVisibility } from "@lib/types"
|
import { PostVisibility } from "@lib/types"
|
||||||
|
|
||||||
// TODO: isOwner should default to false so this can be used generically
|
// TODO: isOwner should default to false so this can be used generically
|
||||||
|
|
|
@ -102,12 +102,8 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
||||||
const isAvailable = !isExpired && !isProtected && post.title
|
const isAvailable = !isExpired && !isProtected && post.title
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page width={"100%"}>
|
<>
|
||||||
<PageSeo
|
|
||||||
title={`${post.title} - Drift`}
|
|
||||||
description={post.description}
|
|
||||||
isPrivate={false}
|
|
||||||
/>
|
|
||||||
{!isAvailable && <PasswordModalPage setPost={setPost} />}
|
{!isAvailable && <PasswordModalPage setPost={setPost} />}
|
||||||
<Page.Content className={homeStyles.main}>
|
<Page.Content className={homeStyles.main}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
|
@ -178,7 +174,7 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
||||||
)}
|
)}
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
</Page.Content>
|
</Page.Content>
|
||||||
</Page>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
'use client';
|
||||||
import { Fieldset, Text, Divider } from "@geist-ui/core/dist"
|
import { Fieldset, Text, Divider } from "@geist-ui/core/dist"
|
||||||
import styles from "./settings-group.module.css"
|
import styles from "./settings-group.module.css"
|
||||||
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import Password from "./sections/password"
|
|
||||||
import Profile from "./sections/profile"
|
|
||||||
import SettingsGroup from "../settings-group"
|
|
||||||
|
|
||||||
const SettingsPage = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "var(--gap)",
|
|
||||||
marginBottom: "var(--gap)"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1>Settings</h1>
|
|
||||||
<SettingsGroup title="Profile">
|
|
||||||
<Profile />
|
|
||||||
</SettingsGroup>
|
|
||||||
<SettingsGroup title="Password">
|
|
||||||
<Password />
|
|
||||||
</SettingsGroup>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SettingsPage
|
|
|
@ -2,21 +2,20 @@
|
||||||
|
|
||||||
import { Note, Input, Textarea, Button, useToasts } from "@geist-ui/core/dist"
|
import { Note, Input, Textarea, Button, useToasts } from "@geist-ui/core/dist"
|
||||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||||
import useUserData from "@lib/hooks/use-user-data"
|
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from "cookies-next"
|
||||||
|
import { User } from "next-auth"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = ({ user }: { user: User }) => {
|
||||||
const user = useUserData()
|
|
||||||
const [name, setName] = useState<string>()
|
const [name, setName] = useState<string>()
|
||||||
const [email, setEmail] = useState<string>()
|
const [email, setEmail] = useState<string>()
|
||||||
const [bio, setBio] = useState<string>()
|
const [bio, setBio] = useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(user)
|
console.log(user)
|
||||||
if (user?.displayName) setName(user.displayName)
|
// if (user?.displayName) setName(user.displayName)
|
||||||
if (user?.email) setEmail(user.email)
|
if (user?.email) setEmail(user.email)
|
||||||
if (user?.bio) setBio(user.bio)
|
// if (user?.bio) setBio(user.bio)
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
const { setToast } = useToasts()
|
const { setToast } = useToasts()
|
||||||
|
|
|
@ -10,6 +10,8 @@ type Config = {
|
||||||
welcome_content: string
|
welcome_content: string
|
||||||
welcome_title: string
|
welcome_title: string
|
||||||
url: string
|
url: string
|
||||||
|
GITHUB_CLIENT_ID: string
|
||||||
|
GITHUB_CLIENT_SECRET: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvironmentValue = string | undefined
|
type EnvironmentValue = string | undefined
|
||||||
|
@ -80,7 +82,9 @@ export const config = (env: Environment): Config => {
|
||||||
registration_password: env.REGISTRATION_PASSWORD ?? "",
|
registration_password: env.REGISTRATION_PASSWORD ?? "",
|
||||||
welcome_content: env.WELCOME_CONTENT ?? "",
|
welcome_content: env.WELCOME_CONTENT ?? "",
|
||||||
welcome_title: env.WELCOME_TITLE ?? "",
|
welcome_title: env.WELCOME_TITLE ?? "",
|
||||||
url: "http://localhost:3000"
|
url: "http://localhost:3000",
|
||||||
|
GITHUB_CLIENT_ID: env.GITHUB_CLIENT_ID ?? "",
|
||||||
|
GITHUB_CLIENT_SECRET: env.GITHUB_CLIENT_SECRET ?? "",
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@ const useSignedIn = () => {
|
||||||
setCookie(TOKEN_COOKIE_NAME, token)
|
setCookie(TOKEN_COOKIE_NAME, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (token) {
|
// if (token) {
|
||||||
setSignedIn(true)
|
// setSignedIn(true)
|
||||||
} else {
|
// } else {
|
||||||
setSignedIn(false)
|
// setSignedIn(false)
|
||||||
}
|
// }
|
||||||
}, [setSignedIn, token])
|
// }, [setSignedIn, token])
|
||||||
|
|
||||||
console.log("signed in", signedIn)
|
console.log("signed in", signedIn)
|
||||||
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
|
||||||
import { User } from "@lib/types"
|
|
||||||
import { deleteCookie, getCookie } from "cookies-next"
|
|
||||||
import { useRouter } from "next/navigation"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
const useUserData = () => {
|
|
||||||
const cookie = getCookie(TOKEN_COOKIE_NAME)
|
|
||||||
const [authToken, setAuthToken] = useState<string>(
|
|
||||||
cookie ? String(cookie) : ""
|
|
||||||
)
|
|
||||||
const [user, setUser] = useState<User>()
|
|
||||||
const router = useRouter()
|
|
||||||
useEffect(() => {
|
|
||||||
const token = getCookie(TOKEN_COOKIE_NAME)
|
|
||||||
if (token) {
|
|
||||||
setAuthToken(String(token))
|
|
||||||
}
|
|
||||||
}, [setAuthToken])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (authToken) {
|
|
||||||
const fetchUser = async () => {
|
|
||||||
const response = await fetch(`/api/user/self`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${authToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (response.ok) {
|
|
||||||
const user = await response.json()
|
|
||||||
setUser(user)
|
|
||||||
} else {
|
|
||||||
// deleteCookie("drift-token")
|
|
||||||
// setAuthToken("")
|
|
||||||
// router.push("/")
|
|
||||||
console.log("not ok")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchUser()
|
|
||||||
}
|
|
||||||
}, [authToken, router])
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useUserData
|
|
61
client/lib/server/auth.ts
Normal file
61
client/lib/server/auth.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||||
|
import { NextAuthOptions } from "next-auth"
|
||||||
|
import GitHubProvider from "next-auth/providers/github"
|
||||||
|
import prisma from "lib/server/prisma"
|
||||||
|
import config from "@lib/config"
|
||||||
|
|
||||||
|
const providers: NextAuthOptions["providers"] = [
|
||||||
|
GitHubProvider({
|
||||||
|
clientId: config.GITHUB_CLIENT_ID,
|
||||||
|
clientSecret: config.GITHUB_CLIENT_SECRET
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
// see https://github.com/prisma/prisma/issues/16117 / https://github.com/shadcn/taxonomy
|
||||||
|
adapter: PrismaAdapter(prisma as any),
|
||||||
|
session: {
|
||||||
|
strategy: "jwt"
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: "/signin"
|
||||||
|
},
|
||||||
|
providers,
|
||||||
|
callbacks: {
|
||||||
|
async session({ token, session }) {
|
||||||
|
if (token) {
|
||||||
|
session.user.id = token.id
|
||||||
|
session.user.name = token.name
|
||||||
|
session.user.email = token.email
|
||||||
|
session.user.image = token.picture
|
||||||
|
session.user.role = token.role
|
||||||
|
}
|
||||||
|
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
async jwt({ token, user }) {
|
||||||
|
const dbUser = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
email: token.email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!dbUser) {
|
||||||
|
// TODO: user should be defined?
|
||||||
|
if (user) {
|
||||||
|
token.id = user.id
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: dbUser.id,
|
||||||
|
name: dbUser.username,
|
||||||
|
email: dbUser.email,
|
||||||
|
picture: dbUser.image,
|
||||||
|
role: dbUser.role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import config from "@lib/config"
|
import config from "@lib/config"
|
||||||
import { User } from "@prisma/client"
|
import { User } from "@prisma/client"
|
||||||
import prisma from "app/prisma"
|
import prisma from "@lib/server/prisma"
|
||||||
import { sign } from "jsonwebtoken"
|
import { sign } from "jsonwebtoken"
|
||||||
|
|
||||||
export async function generateAndExpireAccessToken(userId: User["id"]) {
|
export async function generateAndExpireAccessToken(userId: User["id"]) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import markdown from "../render-markdown"
|
import markdown from "../render-markdown"
|
||||||
import type { File } from "app/prisma"
|
import type { File } from "@lib/server/prisma"
|
||||||
/**
|
/**
|
||||||
* returns rendered HTML from a Drift file
|
* returns rendered HTML from a Drift file
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { useRouter } from "next/navigation"
|
|
||||||
import { isSignedIn } from "../is-signed-in"
|
|
||||||
|
|
||||||
export const useRedirectIfNotAuthed = (to = "/signin") => {
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const signedIn = isSignedIn()
|
|
||||||
|
|
||||||
if (!signedIn) {
|
|
||||||
router.push(to)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { TOKEN_COOKIE_NAME, USER_COOKIE_NAME } from "@lib/constants"
|
|
||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
export const isSignedIn = () => {
|
|
||||||
const cookieList = cookies()
|
|
||||||
return cookieList.has(TOKEN_COOKIE_NAME) && cookieList.has(USER_COOKIE_NAME)
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import config from "@lib/config"
|
import config from "@lib/config"
|
||||||
import { User } from "@prisma/client"
|
import { User } from "@prisma/client"
|
||||||
import prisma from "app/prisma"
|
import prisma from "@lib/server/prisma"
|
||||||
import * as jwt from "jsonwebtoken"
|
import * as jwt from "jsonwebtoken"
|
||||||
import next, { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
|
import next, { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
|
|
173
client/lib/server/prisma.ts
Normal file
173
client/lib/server/prisma.ts
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
declare global {
|
||||||
|
var prisma: PrismaClient | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
import config from "@lib/config"
|
||||||
|
import { Post, PrismaClient, File, User } from "@prisma/client"
|
||||||
|
import { generateAndExpireAccessToken } from "./generate-access-token"
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export default prisma
|
||||||
|
|
||||||
|
// https://next-auth.js.org/adapters/prisma
|
||||||
|
const client = globalThis.prisma || prisma
|
||||||
|
if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||||
|
|
||||||
|
export type { User, AuthTokens, File, Post } from "@prisma/client"
|
||||||
|
|
||||||
|
export type PostWithFiles = Post & {
|
||||||
|
files: File[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFilesForPost = async (postId: string) => {
|
||||||
|
const files = await prisma.file.findMany({
|
||||||
|
where: {
|
||||||
|
postId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When passed in a postId, fetches the post and then the files.
|
||||||
|
* If passed a Post, it will fetch the files
|
||||||
|
* @param postIdOrPost Post or postId
|
||||||
|
* @returns Promise<PostWithFiles>
|
||||||
|
*/
|
||||||
|
export async function getPostWithFiles(postId: string): Promise<PostWithFiles>
|
||||||
|
export async function getPostWithFiles(postObject: Post): Promise<PostWithFiles>
|
||||||
|
export async function getPostWithFiles(
|
||||||
|
postIdOrObject: string | Post
|
||||||
|
): Promise<PostWithFiles | undefined> {
|
||||||
|
let post: Post | null
|
||||||
|
if (typeof postIdOrObject === "string") {
|
||||||
|
post = await prisma.post.findUnique({
|
||||||
|
where: {
|
||||||
|
id: postIdOrObject
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
post = postIdOrObject
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await getFilesForPost(post.id)
|
||||||
|
|
||||||
|
if (!files) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...post,
|
||||||
|
files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPostsByUser(userId: string): Promise<Post[]>
|
||||||
|
export async function getPostsByUser(
|
||||||
|
userId: string,
|
||||||
|
includeFiles: true
|
||||||
|
): Promise<PostWithFiles[]>
|
||||||
|
export async function getPostsByUser(userId: User["id"], withFiles?: boolean) {
|
||||||
|
const posts = await prisma.post.findMany({
|
||||||
|
where: {
|
||||||
|
authorId: userId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (withFiles) {
|
||||||
|
const postsWithFiles = await Promise.all(
|
||||||
|
posts.map(async (post) => {
|
||||||
|
const files = await getPostWithFiles(post)
|
||||||
|
return {
|
||||||
|
...post,
|
||||||
|
files
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return postsWithFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
return posts
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUserById = async (userId: User["id"]) => {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
// displayName: true,
|
||||||
|
role: true,
|
||||||
|
username: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isUserAdmin = async (userId: User["id"]) => {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
role: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return user?.role?.toLowerCase() === "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUser = async (username: string, password: string, serverPassword?: string) => {
|
||||||
|
if (!username || !password) {
|
||||||
|
throw new Error("Missing param")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.registration_password &&
|
||||||
|
serverPassword !== config.registration_password
|
||||||
|
) {
|
||||||
|
console.log("Registration password mismatch")
|
||||||
|
throw new Error("Wrong registration password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// const salt = await genSalt(10)
|
||||||
|
|
||||||
|
// the first user is the admin
|
||||||
|
const isUserAdminByDefault = config.enable_admin && (await prisma.user.count()) === 0
|
||||||
|
const userRole = isUserAdminByDefault ? "admin" : "user"
|
||||||
|
|
||||||
|
// const user = await prisma.user.create({
|
||||||
|
// data: {
|
||||||
|
// username,
|
||||||
|
// password: await bcrypt.hash(password, salt),
|
||||||
|
// role: userRole,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const token = await generateAndExpireAccessToken(user.id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// user,
|
||||||
|
// token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostById = async (postId: Post["id"]) => {
|
||||||
|
const post = await prisma.post.findUnique({
|
||||||
|
where: {
|
||||||
|
id: postId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
15
client/lib/server/session.ts
Normal file
15
client/lib/server/session.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
import 'server-only';
|
||||||
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
|
import { authOptions } from "./auth"
|
||||||
|
|
||||||
|
|
||||||
|
export async function getSession() {
|
||||||
|
return await unstable_getServerSession(authOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentUser() {
|
||||||
|
const session = await getSession()
|
||||||
|
|
||||||
|
return session?.user
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { USER_COOKIE_NAME, TOKEN_COOKIE_NAME } from "@lib/constants"
|
import { USER_COOKIE_NAME, TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||||
import { User } from "app/prisma"
|
import { User } from "@lib/server/prisma"
|
||||||
import { setCookie } from "cookies-next"
|
import { setCookie } from "cookies-next"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
import { generateAndExpireAccessToken } from "./generate-access-token"
|
import { generateAndExpireAccessToken } from "./generate-access-token"
|
||||||
|
|
|
@ -1,68 +1,38 @@
|
||||||
import { NextFetchEvent, NextResponse } from "next/server"
|
import { getToken } from "next-auth/jwt"
|
||||||
import type { NextRequest } from "next/server"
|
import { withAuth } from "next-auth/middleware"
|
||||||
import { TOKEN_COOKIE_NAME, USER_COOKIE_NAME } from "@lib/constants"
|
import { NextResponse } from "next/server"
|
||||||
import serverConfig from "@lib/config"
|
|
||||||
|
|
||||||
const PUBLIC_FILE = /\.(.*)$/
|
export default withAuth(
|
||||||
|
async function middleware(req) {
|
||||||
|
const token = await getToken({ req })
|
||||||
|
const isAuth = !!token
|
||||||
|
const isAuthPage =
|
||||||
|
req.nextUrl.pathname.startsWith("/signup") ||
|
||||||
|
req.nextUrl.pathname.startsWith("/signin")
|
||||||
|
|
||||||
export function middleware(req: NextRequest, event: NextFetchEvent) {
|
if (isAuthPage) {
|
||||||
const pathname = req.nextUrl.pathname
|
if (isAuth) {
|
||||||
const signedIn = req.cookies.get(TOKEN_COOKIE_NAME)
|
return NextResponse.redirect(new URL("/new", req.url))
|
||||||
const getURL = (pageName: string) => new URL(`/${pageName}`, req.url).href
|
}
|
||||||
const isPageRequest =
|
|
||||||
!PUBLIC_FILE.test(pathname) &&
|
|
||||||
// header added when next/link pre-fetches a route
|
|
||||||
!req.headers.get("x-middleware-preflight")
|
|
||||||
|
|
||||||
if (!req.headers.get("x-middleware-preflight") && pathname === "/signout") {
|
return null
|
||||||
// If you're signed in we remove the cookie and redirect to the home page
|
|
||||||
// If you're not signed in we redirect to the home page
|
|
||||||
if (signedIn) {
|
|
||||||
const resp = NextResponse.redirect(getURL(""))
|
|
||||||
resp.cookies.delete(TOKEN_COOKIE_NAME)
|
|
||||||
resp.cookies.delete(USER_COOKIE_NAME)
|
|
||||||
const signoutPromise = new Promise((resolve) => {
|
|
||||||
fetch(`${serverConfig.url}/auth/signout`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${signedIn}`,
|
|
||||||
"x-secret-key": process.env.SECRET_KEY || ""
|
|
||||||
}
|
|
||||||
}).then(() => {
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
event.waitUntil(signoutPromise)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
}
|
}
|
||||||
} else if (isPageRequest) {
|
|
||||||
// if (signedIn) {
|
|
||||||
// if (
|
|
||||||
// pathname === "/" ||
|
|
||||||
// pathname === "/signin" ||
|
|
||||||
// pathname === "/signup"
|
|
||||||
// ) {
|
|
||||||
// return NextResponse.redirect(getURL("new"))
|
|
||||||
// }
|
|
||||||
// } else if (!signedIn) {
|
|
||||||
// if (pathname.startsWith("/new")) {
|
|
||||||
// return NextResponse.redirect(getURL("signin"))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (pathname.includes("/protected/") || pathname.includes("/private/")) {
|
if (!isAuth) {
|
||||||
const urlWithoutVisibility = pathname
|
return NextResponse.redirect(new URL("/signin", req.url))
|
||||||
.replace("/protected/", "/")
|
}
|
||||||
.replace("/private/", "/")
|
},
|
||||||
.substring(1)
|
{
|
||||||
return NextResponse.redirect(getURL(urlWithoutVisibility))
|
callbacks: {
|
||||||
|
async authorized() {
|
||||||
|
// This is a work-around for handling redirect on auth pages.
|
||||||
|
// We return true here so that the middleware function above
|
||||||
|
// is always called.
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
return NextResponse.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
match: [
|
match: [
|
||||||
|
@ -71,9 +41,6 @@ export const config = {
|
||||||
"/signin",
|
"/signin",
|
||||||
"/signup",
|
"/signup",
|
||||||
"/new",
|
"/new",
|
||||||
"/protected/:path*",
|
|
||||||
"/private/:path*"
|
"/private/:path*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@geist-ui/core": "^2.3.8",
|
"@geist-ui/core": "^2.3.8",
|
||||||
"@geist-ui/icons": "1.0.2",
|
"@geist-ui/icons": "1.0.2",
|
||||||
"@prisma/client": "^4.6.0",
|
"@next-auth/prisma-adapter": "^1.0.5",
|
||||||
|
"@prisma/client": "^4.6.1",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"client-zip": "2.2.1",
|
"client-zip": "2.2.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
@ -22,7 +23,8 @@
|
||||||
"dotenv": "16.0.0",
|
"dotenv": "16.0.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"marked": "^4.2.2",
|
"marked": "^4.2.2",
|
||||||
"next": "13.0.3-canary.2",
|
"next": "13.0.3-canary.4",
|
||||||
|
"next-auth": "^4.16.4",
|
||||||
"next-themes": "npm:@wits/next-themes@0.2.7",
|
"next-themes": "npm:@wits/next-themes@0.2.7",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"prism-react-renderer": "^1.3.5",
|
||||||
"rc-table": "7.24.1",
|
"rc-table": "7.24.1",
|
||||||
|
@ -32,6 +34,7 @@
|
||||||
"react-dropzone": "14.2.3",
|
"react-dropzone": "14.2.3",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-loading-skeleton": "3.1.0",
|
"react-loading-skeleton": "3.1.0",
|
||||||
|
"server-only": "^0.0.1",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"swr": "1.3.0",
|
"swr": "1.3.0",
|
||||||
"textarea-markdown-editor": "0.1.13",
|
"textarea-markdown-editor": "0.1.13",
|
||||||
|
@ -52,7 +55,7 @@
|
||||||
"eslint-config-next": "13.0.2",
|
"eslint-config-next": "13.0.2",
|
||||||
"next-unused": "0.0.6",
|
"next-unused": "0.0.6",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"prisma": "^4.6.0",
|
"prisma": "^4.6.1",
|
||||||
"typescript": "4.6.4",
|
"typescript": "4.6.4",
|
||||||
"typescript-plugin-css-modules": "3.4.0"
|
"typescript-plugin-css-modules": "3.4.0"
|
||||||
},
|
},
|
||||||
|
|
4
client/pages/api/auth/[...nextauth].ts
Normal file
4
client/pages/api/auth/[...nextauth].ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { authOptions } from "@lib/server/auth"
|
||||||
|
import NextAuth from "next-auth"
|
||||||
|
|
||||||
|
export default NextAuth(authOptions)
|
|
@ -1,7 +1,9 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
import prisma from "app/prisma"
|
import prisma from "@lib/server/prisma"
|
||||||
import bcrypt from "bcrypt"
|
import bcrypt from "bcrypt"
|
||||||
import { signin } from "@lib/server/signin"
|
import { signin } from "@lib/server/signin"
|
||||||
|
import { setCookie } from "cookies-next"
|
||||||
|
import { TOKEN_COOKIE_NAME, USER_COOKIE_NAME } from "@lib/constants"
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
@ -18,7 +20,7 @@ export default async function handler(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.password) {
|
||||||
return res.status(401).json({ error: "Unauthorized" })
|
return res.status(401).json({ error: "Unauthorized" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +29,23 @@ export default async function handler(
|
||||||
return res.status(401).json({ error: "Unauthorized" })
|
return res.status(401).json({ error: "Unauthorized" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await signin(user.id, req, res);
|
const token = await signin(user.id, req, res)
|
||||||
|
setCookie(TOKEN_COOKIE_NAME, token, {
|
||||||
|
path: "/",
|
||||||
|
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
})
|
||||||
|
setCookie(USER_COOKIE_NAME, user.id, {
|
||||||
|
path: "/",
|
||||||
|
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
})
|
||||||
|
|
||||||
return res.status(201).json({ token: token, userId: user.id })
|
return res.status(201).json({ token: token, userId: user.id })
|
||||||
}
|
}
|
12
client/pages/api/auth/signup-backup.ts
Normal file
12
client/pages/api/auth/signup-backup.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
import { createUser } from "@lib/server/prisma"
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
const { username, password, serverPassword } = req.body
|
||||||
|
const { user, token } = await createUser(username, password, serverPassword)
|
||||||
|
|
||||||
|
return res.status(201).json({ token: token, userId: user.id })
|
||||||
|
}
|
|
@ -1,45 +0,0 @@
|
||||||
import config from "@lib/config"
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
|
||||||
import prisma from "app/prisma"
|
|
||||||
import bcrypt, { genSalt } from "bcrypt"
|
|
||||||
import { generateAndExpireAccessToken } from "@lib/server/generate-access-token"
|
|
||||||
|
|
||||||
export default async function handler(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse
|
|
||||||
) {
|
|
||||||
const { username, password, serverPassword } = req.body
|
|
||||||
if (!username || !password) {
|
|
||||||
return res.status(400).json({ error: "Missing param" })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
config.registration_password &&
|
|
||||||
serverPassword !== config.registration_password
|
|
||||||
) {
|
|
||||||
console.log("Registration password mismatch")
|
|
||||||
return res.status(401).json({ error: "Unauthorized" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const salt = await genSalt(10)
|
|
||||||
|
|
||||||
// the first user is the admin
|
|
||||||
const isUserAdminByDefault = config.enable_admin && (await prisma.user.count()) === 0
|
|
||||||
const userRole = isUserAdminByDefault ? "admin" : "user"
|
|
||||||
|
|
||||||
const user = await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
username,
|
|
||||||
password: await bcrypt.hash(password, salt),
|
|
||||||
role: userRole
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const token = await generateAndExpireAccessToken(user.id)
|
|
||||||
|
|
||||||
return res.status(201).json({ token: token, userId: user.id })
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file"
|
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file"
|
||||||
import { parseQueryParam } from "@lib/server/parse-query-param"
|
import { parseQueryParam } from "@lib/server/parse-query-param"
|
||||||
import prisma from "app/prisma"
|
import prisma from "@lib/server/prisma"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { parseQueryParam } from "@lib/server/parse-query-param"
|
import { parseQueryParam } from "@lib/server/parse-query-param"
|
||||||
import { getPostsByUser } from "app/prisma"
|
import { getPostsByUser } from "@lib/server/prisma"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
export default async function handle(
|
export default async function handle(
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// })
|
// })
|
||||||
|
|
||||||
import { USER_COOKIE_NAME } from "@lib/constants"
|
import { USER_COOKIE_NAME } from "@lib/constants"
|
||||||
import prisma, { getUserById } from "app/prisma"
|
import prisma, { getUserById } from "@lib/server/prisma"
|
||||||
import { getCookie } from "cookies-next"
|
import { getCookie } from "cookies-next"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ lockfileVersion: 5.4
|
||||||
specifiers:
|
specifiers:
|
||||||
'@geist-ui/core': ^2.3.8
|
'@geist-ui/core': ^2.3.8
|
||||||
'@geist-ui/icons': 1.0.2
|
'@geist-ui/icons': 1.0.2
|
||||||
|
'@next-auth/prisma-adapter': ^1.0.5
|
||||||
'@next/bundle-analyzer': 12.1.6
|
'@next/bundle-analyzer': 12.1.6
|
||||||
'@prisma/client': ^4.6.0
|
'@prisma/client': ^4.6.1
|
||||||
'@types/bcrypt': ^5.0.0
|
'@types/bcrypt': ^5.0.0
|
||||||
'@types/jsonwebtoken': ^8.5.9
|
'@types/jsonwebtoken': ^8.5.9
|
||||||
'@types/marked': ^4.0.7
|
'@types/marked': ^4.0.7
|
||||||
|
@ -23,12 +24,13 @@ specifiers:
|
||||||
eslint-config-next: 13.0.2
|
eslint-config-next: 13.0.2
|
||||||
jsonwebtoken: ^8.5.1
|
jsonwebtoken: ^8.5.1
|
||||||
marked: ^4.2.2
|
marked: ^4.2.2
|
||||||
next: 13.0.3-canary.2
|
next: 13.0.3-canary.4
|
||||||
|
next-auth: ^4.16.4
|
||||||
next-themes: npm:@wits/next-themes@0.2.7
|
next-themes: npm:@wits/next-themes@0.2.7
|
||||||
next-unused: 0.0.6
|
next-unused: 0.0.6
|
||||||
prettier: 2.6.2
|
prettier: 2.6.2
|
||||||
prism-react-renderer: ^1.3.5
|
prism-react-renderer: ^1.3.5
|
||||||
prisma: ^4.6.0
|
prisma: ^4.6.1
|
||||||
rc-table: 7.24.1
|
rc-table: 7.24.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-datepicker: 4.8.0
|
react-datepicker: 4.8.0
|
||||||
|
@ -36,6 +38,7 @@ specifiers:
|
||||||
react-dropzone: 14.2.3
|
react-dropzone: 14.2.3
|
||||||
react-hot-toast: ^2.4.0
|
react-hot-toast: ^2.4.0
|
||||||
react-loading-skeleton: 3.1.0
|
react-loading-skeleton: 3.1.0
|
||||||
|
server-only: ^0.0.1
|
||||||
sharp: ^0.31.2
|
sharp: ^0.31.2
|
||||||
showdown: ^2.1.0
|
showdown: ^2.1.0
|
||||||
swr: 1.3.0
|
swr: 1.3.0
|
||||||
|
@ -47,7 +50,8 @@ specifiers:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@geist-ui/core': 2.3.8_biqbaboplfbrettd7655fr4n2y
|
'@geist-ui/core': 2.3.8_biqbaboplfbrettd7655fr4n2y
|
||||||
'@geist-ui/icons': 1.0.2_zhza2kbnl2wkkf7vqdl3ton2f4
|
'@geist-ui/icons': 1.0.2_zhza2kbnl2wkkf7vqdl3ton2f4
|
||||||
'@prisma/client': 4.6.0_prisma@4.6.0
|
'@next-auth/prisma-adapter': 1.0.5_2pl3b2nwmjya7el2zbe6cwkney
|
||||||
|
'@prisma/client': 4.6.1_prisma@4.6.1
|
||||||
bcrypt: 5.1.0
|
bcrypt: 5.1.0
|
||||||
client-zip: 2.2.1
|
client-zip: 2.2.1
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
|
@ -55,8 +59,9 @@ dependencies:
|
||||||
dotenv: 16.0.0
|
dotenv: 16.0.0
|
||||||
jsonwebtoken: 8.5.1
|
jsonwebtoken: 8.5.1
|
||||||
marked: 4.2.2
|
marked: 4.2.2
|
||||||
next: 13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y
|
next: 13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y
|
||||||
next-themes: /@wits/next-themes/0.2.7_qjr36eup74ongf7bl2iopfchwe
|
next-auth: 4.16.4_hsmqkug4agizydugca45idewda
|
||||||
|
next-themes: /@wits/next-themes/0.2.7_hsmqkug4agizydugca45idewda
|
||||||
prism-react-renderer: 1.3.5_react@18.2.0
|
prism-react-renderer: 1.3.5_react@18.2.0
|
||||||
rc-table: 7.24.1_biqbaboplfbrettd7655fr4n2y
|
rc-table: 7.24.1_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
@ -65,6 +70,7 @@ dependencies:
|
||||||
react-dropzone: 14.2.3_react@18.2.0
|
react-dropzone: 14.2.3_react@18.2.0
|
||||||
react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y
|
react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y
|
||||||
react-loading-skeleton: 3.1.0_react@18.2.0
|
react-loading-skeleton: 3.1.0_react@18.2.0
|
||||||
|
server-only: 0.0.1
|
||||||
showdown: 2.1.0
|
showdown: 2.1.0
|
||||||
swr: 1.3.0_react@18.2.0
|
swr: 1.3.0_react@18.2.0
|
||||||
textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y
|
textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y
|
||||||
|
@ -88,7 +94,7 @@ devDependencies:
|
||||||
eslint-config-next: 13.0.2_hsmo2rtalirsvadpuxki35bq2i
|
eslint-config-next: 13.0.2_hsmo2rtalirsvadpuxki35bq2i
|
||||||
next-unused: 0.0.6
|
next-unused: 0.0.6
|
||||||
prettier: 2.6.2
|
prettier: 2.6.2
|
||||||
prisma: 4.6.0
|
prisma: 4.6.1
|
||||||
typescript: 4.6.4
|
typescript: 4.6.4
|
||||||
typescript-plugin-css-modules: 3.4.0_typescript@4.6.4
|
typescript-plugin-css-modules: 3.4.0_typescript@4.6.4
|
||||||
|
|
||||||
|
@ -211,6 +217,16 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@next-auth/prisma-adapter/1.0.5_2pl3b2nwmjya7el2zbe6cwkney:
|
||||||
|
resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@prisma/client': '>=2.26.0 || >=3'
|
||||||
|
next-auth: ^4
|
||||||
|
dependencies:
|
||||||
|
'@prisma/client': 4.6.1_prisma@4.6.1
|
||||||
|
next-auth: 4.16.4_hsmqkug4agizydugca45idewda
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@next/bundle-analyzer/12.1.6:
|
/@next/bundle-analyzer/12.1.6:
|
||||||
resolution: {integrity: sha512-WLydwytAeHoC/neXsiIgK+a6Me12PuSpwopnsZgX5JFNwXQ9MlwPeMGS3aTZkYsv8QmSm0Ns9Yh9FkgLKYaUuQ==}
|
resolution: {integrity: sha512-WLydwytAeHoC/neXsiIgK+a6Me12PuSpwopnsZgX5JFNwXQ9MlwPeMGS3aTZkYsv8QmSm0Ns9Yh9FkgLKYaUuQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -220,8 +236,8 @@ packages:
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/env/13.0.3-canary.2:
|
/@next/env/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-Ugn4VxB+2Bd1LnWcMbjIwNcVYPoBZ8Yo6j2A3MU99pzeYq+TGtHcYPz0xyIAP3Qp7mrH5gx6PITVz7D22u8p7w==}
|
resolution: {integrity: sha512-IKMYPznB0ttgHa1K7nKbfSMM8kne3G7Am+eNeM11cr+HjPljAzl863Ib9UBk6s7oChTAEVtaoKHbAerW/36tWA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next/13.0.2:
|
/@next/eslint-plugin-next/13.0.2:
|
||||||
|
@ -230,8 +246,8 @@ packages:
|
||||||
glob: 7.1.7
|
glob: 7.1.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/swc-android-arm-eabi/13.0.3-canary.2:
|
/@next/swc-android-arm-eabi/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-ZZG0C+P4czfq5Zyhdouacb3w73w/iOj4KidWCpWlYfTnxlMinPoEDk04xFg5iR665ePlS2mrBnj2OfhckYcFdQ==}
|
resolution: {integrity: sha512-3CXPHZfP7KGwKlrBv451x3l++q1Jxr/5PESk1TkFednJmw+9F6Tno+2RPYEzE++EWxjuAM8SmwHZxhJ6HorOvA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -239,8 +255,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-android-arm64/13.0.3-canary.2:
|
/@next/swc-android-arm64/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-0Nw4n6Eox1cCp0d9BJ5GQDgW2+8JxoF5asdOdN0E1a6ayygOfsXN/GP3VWcrpLSrx6K1XUO+lgBbCbaOjvnoxA==}
|
resolution: {integrity: sha512-hjsSok+41ZYDghIXMUrvv1eyDboinpDu5kcd/aQTqiV9ukuoQSQFwPd9i8fXVWKOb8w9rfoSLrPoslZXxbMolw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -248,8 +264,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-arm64/13.0.3-canary.2:
|
/@next/swc-darwin-arm64/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-TkSQVEEcmCfbzotHNHGWe1PkiZZkKPg4QWylZYv8UDfRUwJwR94aJeriOqlGOTkKQ/6a+ulJrVgs50/5gTTIHg==}
|
resolution: {integrity: sha512-DxpeUXj7UcSidRDH0WjDzFlrycNvCKtQgpjPEzljBs2MGXGisuJ/znFkmqbLYwUi71La0nw91Yuz7IrGDpbhag==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -257,8 +273,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64/13.0.3-canary.2:
|
/@next/swc-darwin-x64/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-hGCarEZsaSdOWtJOUJc4Sr3oRzUjlI/G+qlyMkaceSTyYx4Xu2/OmDS1fCWxoltlimiHmlJpLnGGaxUgrZ8dkQ==}
|
resolution: {integrity: sha512-jGdLe9QRpbSMkO+Ttpr8fnl2q/s1cQuBvGKM0nHiIUtwuwnho4BjcYQdcCJbjjH2Vs0KMhayZh9REa+52vdAEA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -266,8 +282,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-freebsd-x64/13.0.3-canary.2:
|
/@next/swc-freebsd-x64/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-R7WFI/whtuSB6gxmzgqFzeKbrhuSp3ut0GaQK+kvb7NUnFe9xABUksdxEU8bORjVJaADgDsCsCHSsHGqHHl7Mg==}
|
resolution: {integrity: sha512-9VJCLOkbteSozo8kxrqiFJDntARLIn0Uv4aXdvbAuYhEIVRbnP0uA3z1r6d4g8ycC1Yout6z0m3pkg0MHbKV2w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
@ -275,8 +291,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm-gnueabihf/13.0.3-canary.2:
|
/@next/swc-linux-arm-gnueabihf/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-RzYf+MTdP8Rvz/fijlxsTP+1S24ziMtCtzq2Ui8Qjg7VIfD9sEuLmMQJpm0k/FscduQdZILoG+QNhD2oW893Wg==}
|
resolution: {integrity: sha512-SBA6Ja07guZI8KnIpMRN6tDvD6tse70c8d9HPwdkK7JziwIBzNDSuLbuA9WB+9/byM70U8jROBKgMUZAsAbnew==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -284,8 +300,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu/13.0.3-canary.2:
|
/@next/swc-linux-arm64-gnu/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-8TF9UxIAZuQNf4fkyfZ1LcrqqvRI2Li0V2IO0CiCx4wg6xDqBjMH3IZoRwgY3yJ8UxdrFWf8Ec1q2WBYXcODgQ==}
|
resolution: {integrity: sha512-9hQU3mZtzuLAvqaz/72jM2IWtV3lcLFhWqWGCS8yqUCKjkT2ppd/L/VEVuvatC67H5wzpbAJPnDxjPIl7ryiOA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -293,8 +309,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl/13.0.3-canary.2:
|
/@next/swc-linux-arm64-musl/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-Ll2nV3pbCi3qL9o+6zxEuQAqqk8yPLk1TJ7+G8fTmm1vpjMjdV8eBiXiZVGyweRBhurhHmeSdh9JtpUFuPvDRA==}
|
resolution: {integrity: sha512-iZTyAMbQiI0kng46mVp9XKscv59STqLbIVs6pSD3pnrBqKUh4SECQ6Z2r6Y4/H65ig64x6hvdk3KbG71UU+Kaw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -302,8 +318,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu/13.0.3-canary.2:
|
/@next/swc-linux-x64-gnu/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-TARNMLz9+Ab2rEiuk/ulYULLDWw6zMc4yH2vFXdwckod9tWUyxptAMUz2umtKwyf6lmYUv4+IfZPJgUs0lr5Bw==}
|
resolution: {integrity: sha512-2yYi/bjxf5jHJPTvnC6WbomgETkLWaNY+CEC2Ci1HV3xNVm1/4LiKB0KoDZGUWMBDjAQHO9LmTZS8+P4Q/wubA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -311,8 +327,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl/13.0.3-canary.2:
|
/@next/swc-linux-x64-musl/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-Wbd1Ufm9NRSf+xl9kOfe5St06xHN1DHT0KrQc+cT2QKn9ZavASM/Vu2PM3gt4T/2Gqdv663WdbpEuX97wn3abQ==}
|
resolution: {integrity: sha512-DuT/jlTSyZDMPWDWpVqxkLJqGytXYnbIbZ8T+XRbOihDy8p4HwaKZW9ZcHM04lSnOmxwXFHRR5Exx4y5cQOH+A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -320,8 +336,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc/13.0.3-canary.2:
|
/@next/swc-win32-arm64-msvc/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-d/SiJzQvm+ggFhCBly4VuOUio0OXx5NKLabSw9AcxEK11/V6YGEFNVdPw1q059/eBi3S0mlRBBnowKuJiWGbtg==}
|
resolution: {integrity: sha512-TW1wLzOorp0IhBf2u1XiJ+8OmGWSUID8zWISwyW74oWuNIhpvzbgmCbjFqlfX9xUxAY6tVcx2TfOc5lmsIoaEg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -329,8 +345,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc/13.0.3-canary.2:
|
/@next/swc-win32-ia32-msvc/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-HytAShDnSnY1FkCpsy+t2V09H1Z9ydeZeg8QrLwub26bPWAcDZe77ECVR4rdIHqP4KHBwtAOM8UIZWrexlLggw==}
|
resolution: {integrity: sha512-cCbuBq8ua9u/bpJ0TvyTrEZXNhrzR0R0z/h3gitw+8VUQG4xREwfn3od0J9XjeL0RQ4QbtgorVE2yw9JZ5pOdg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -338,8 +354,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc/13.0.3-canary.2:
|
/@next/swc-win32-x64-msvc/13.0.3-canary.4:
|
||||||
resolution: {integrity: sha512-TPH7wQSLXbeWuwkGFASMkCmE2Q7Tt/S8gTOgC0Y4rJf1yw5K+YtubTZKmmEZ13Aq+fQtqg3NkPO9Rrq4OZpuGw==}
|
resolution: {integrity: sha512-kWfN2WhqxwkaySEddUjm2xJKKdeaIE1/UZXFNCaU5aSZFTn1I4yVjjL40tMCfcppqYbY58X6c5UocOviLcKbrg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -368,6 +384,10 @@ packages:
|
||||||
fastq: 1.13.0
|
fastq: 1.13.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@panva/hkdf/1.0.2:
|
||||||
|
resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@polka/url/1.0.0-next.21:
|
/@polka/url/1.0.0-next.21:
|
||||||
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -375,8 +395,8 @@ packages:
|
||||||
/@popperjs/core/2.11.6:
|
/@popperjs/core/2.11.6:
|
||||||
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
||||||
|
|
||||||
/@prisma/client/4.6.0_prisma@4.6.0:
|
/@prisma/client/4.6.1_prisma@4.6.1:
|
||||||
resolution: {integrity: sha512-D9LaQinDxOHinRpcJTw2tjMtjhc9HTP+aF1IRd2oLldp/8TiwIfxK8x17OhBBiX4y1PzbJXXET7kS+5wB3es/w==}
|
resolution: {integrity: sha512-M1+NNrMzqaOIxT7PBGcTs3IZo7d1EW/+gVQd4C4gUgWBDGgD9AcIeZnUSidgWClmpMSgVUdnVORjsWWGUameYA==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -385,16 +405,16 @@ packages:
|
||||||
prisma:
|
prisma:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/engines-version': 4.6.0-53.2e719efb80b56a3f32d18a62489de95bb9c130e3
|
'@prisma/engines-version': 4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32
|
||||||
prisma: 4.6.0
|
prisma: 4.6.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@prisma/engines-version/4.6.0-53.2e719efb80b56a3f32d18a62489de95bb9c130e3:
|
/@prisma/engines-version/4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32:
|
||||||
resolution: {integrity: sha512-0CTnfEuUbLlO6n1fM89ERDbSwI4LoyZn+1OKVSwG+aVqohj34+mXRfwOWIM0ONtYtLGGBpddvQAnAZkg+cgS6g==}
|
resolution: {integrity: sha512-HUCmkXAU2jqp2O1RvNtbE+seLGLyJGEABZS/R38rZjSAafAy0WzBuHq+tbZMnD+b5OSCsTVtIPVcuvx1ySxcWQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@prisma/engines/4.6.0:
|
/@prisma/engines/4.6.1:
|
||||||
resolution: {integrity: sha512-S+72PAl0zTCbIGou1uXD/McvzdtP+bjOs0LRmGZfcOQcVqR9x/0f6Z+dqpUU0zIcqHEl+0DOB8UXaTwRvssFsQ==}
|
resolution: {integrity: sha512-3u2/XxvxB+Q7cMXHnKU0CpBiUK1QWqpgiBv28YDo1zOIJE3FCF8DI2vrp6vuwjGt5h0JGXDSvmSf4D4maVjJdw==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
|
||||||
/@rushstack/eslint-patch/1.2.0:
|
/@rushstack/eslint-patch/1.2.0:
|
||||||
|
@ -573,14 +593,14 @@ packages:
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@wits/next-themes/0.2.7_qjr36eup74ongf7bl2iopfchwe:
|
/@wits/next-themes/0.2.7_hsmqkug4agizydugca45idewda:
|
||||||
resolution: {integrity: sha512-CpmNH3RRqf2w0i1Xbrz5GKNE/d5gMq1oBlGpofY9LWcjH225nUgrxP15wKRITRAbn68ERDbsBGEBiaRECTmQag==}
|
resolution: {integrity: sha512-CpmNH3RRqf2w0i1Xbrz5GKNE/d5gMq1oBlGpofY9LWcjH225nUgrxP15wKRITRAbn68ERDbsBGEBiaRECTmQag==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: '*'
|
next: '*'
|
||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y
|
next: 13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -973,6 +993,11 @@ packages:
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/cookie/0.5.0:
|
||||||
|
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cookies-next/2.1.1:
|
/cookies-next/2.1.1:
|
||||||
resolution: {integrity: sha512-AZGZPdL1hU3jCjN2UMJTGhLOYzNUN9Gm+v8BdptYIHUdwz397Et1p+sZRfvAl8pKnnmMdX2Pk9xDRKCGBum6GA==}
|
resolution: {integrity: sha512-AZGZPdL1hU3jCjN2UMJTGhLOYzNUN9Gm+v8BdptYIHUdwz397Et1p+sZRfvAl8pKnnmMdX2Pk9xDRKCGBum6GA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2271,6 +2296,10 @@ packages:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jose/4.11.0:
|
||||||
|
resolution: {integrity: sha512-wLe+lJHeG8Xt6uEubS4x0LVjS/3kXXu9dGoj9BNnlhYq7Kts0Pbb2pvv5KiI0yaKH/eaiR0LUOBhOVo9ktd05A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-sdsl/4.1.5:
|
/js-sdsl/4.1.5:
|
||||||
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
|
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2677,6 +2706,32 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/next-auth/4.16.4_hsmqkug4agizydugca45idewda:
|
||||||
|
resolution: {integrity: sha512-KXW578+ER1u5RcWLwCHMdb/RIBIO6JM8r6xlf9RIPSKzkvDcX9FHiZfJS2vOq/SurHXPJZc4J3OS4IDJpF74Dw==}
|
||||||
|
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
|
||||||
|
peerDependencies:
|
||||||
|
next: ^12.2.5 || ^13
|
||||||
|
nodemailer: ^6.6.5
|
||||||
|
react: ^17.0.2 || ^18
|
||||||
|
react-dom: ^17.0.2 || ^18
|
||||||
|
peerDependenciesMeta:
|
||||||
|
nodemailer:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.20.1
|
||||||
|
'@panva/hkdf': 1.0.2
|
||||||
|
cookie: 0.5.0
|
||||||
|
jose: 4.11.0
|
||||||
|
next: 13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y
|
||||||
|
oauth: 0.9.15
|
||||||
|
openid-client: 5.3.0
|
||||||
|
preact: 10.11.2
|
||||||
|
preact-render-to-string: 5.2.6_preact@10.11.2
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
uuid: 8.3.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/next-unused/0.0.6:
|
/next-unused/0.0.6:
|
||||||
resolution: {integrity: sha512-dHFNNBanFq4wvYrULtsjfWyZ6BzOnr5VYI9EYMGAZYF2vkAhFpj2JOuT5Wu2o3LbFSG92PmAZnSUF/LstF82pA==}
|
resolution: {integrity: sha512-dHFNNBanFq4wvYrULtsjfWyZ6BzOnr5VYI9EYMGAZYF2vkAhFpj2JOuT5Wu2o3LbFSG92PmAZnSUF/LstF82pA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -2688,8 +2743,8 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next/13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y:
|
/next/13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-Qr19ElEa+ljqu56t4AoiZ6uld7jvMa9KbDFhXBcKQQ4/DaRGvLsoWDw9l3QADBhsFSegAon0NE7eI1IAP+M1pQ==}
|
resolution: {integrity: sha512-GCf0loggwGvPXeDfYMtg36HByukmALnldQZMIdQnGcJtFHRQsWrprvrTEfqTENU5UOZSYbTdJRdL1Y8QOyymWw==}
|
||||||
engines: {node: '>=14.6.0'}
|
engines: {node: '>=14.6.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2706,7 +2761,7 @@ packages:
|
||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 13.0.3-canary.2
|
'@next/env': 13.0.3-canary.4
|
||||||
'@swc/helpers': 0.4.11
|
'@swc/helpers': 0.4.11
|
||||||
caniuse-lite: 1.0.30001431
|
caniuse-lite: 1.0.30001431
|
||||||
postcss: 8.4.14
|
postcss: 8.4.14
|
||||||
|
@ -2715,19 +2770,19 @@ packages:
|
||||||
styled-jsx: 5.1.0_react@18.2.0
|
styled-jsx: 5.1.0_react@18.2.0
|
||||||
use-sync-external-store: 1.2.0_react@18.2.0
|
use-sync-external-store: 1.2.0_react@18.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-android-arm-eabi': 13.0.3-canary.2
|
'@next/swc-android-arm-eabi': 13.0.3-canary.4
|
||||||
'@next/swc-android-arm64': 13.0.3-canary.2
|
'@next/swc-android-arm64': 13.0.3-canary.4
|
||||||
'@next/swc-darwin-arm64': 13.0.3-canary.2
|
'@next/swc-darwin-arm64': 13.0.3-canary.4
|
||||||
'@next/swc-darwin-x64': 13.0.3-canary.2
|
'@next/swc-darwin-x64': 13.0.3-canary.4
|
||||||
'@next/swc-freebsd-x64': 13.0.3-canary.2
|
'@next/swc-freebsd-x64': 13.0.3-canary.4
|
||||||
'@next/swc-linux-arm-gnueabihf': 13.0.3-canary.2
|
'@next/swc-linux-arm-gnueabihf': 13.0.3-canary.4
|
||||||
'@next/swc-linux-arm64-gnu': 13.0.3-canary.2
|
'@next/swc-linux-arm64-gnu': 13.0.3-canary.4
|
||||||
'@next/swc-linux-arm64-musl': 13.0.3-canary.2
|
'@next/swc-linux-arm64-musl': 13.0.3-canary.4
|
||||||
'@next/swc-linux-x64-gnu': 13.0.3-canary.2
|
'@next/swc-linux-x64-gnu': 13.0.3-canary.4
|
||||||
'@next/swc-linux-x64-musl': 13.0.3-canary.2
|
'@next/swc-linux-x64-musl': 13.0.3-canary.4
|
||||||
'@next/swc-win32-arm64-msvc': 13.0.3-canary.2
|
'@next/swc-win32-arm64-msvc': 13.0.3-canary.4
|
||||||
'@next/swc-win32-ia32-msvc': 13.0.3-canary.2
|
'@next/swc-win32-ia32-msvc': 13.0.3-canary.4
|
||||||
'@next/swc-win32-x64-msvc': 13.0.3-canary.2
|
'@next/swc-win32-x64-msvc': 13.0.3-canary.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
@ -2786,10 +2841,19 @@ packages:
|
||||||
set-blocking: 2.0.0
|
set-blocking: 2.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/oauth/0.9.15:
|
||||||
|
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/object-assign/4.1.1:
|
/object-assign/4.1.1:
|
||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
/object-hash/2.2.0:
|
||||||
|
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/object-inspect/1.12.2:
|
/object-inspect/1.12.2:
|
||||||
resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
|
resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2843,6 +2907,11 @@ packages:
|
||||||
es-abstract: 1.20.4
|
es-abstract: 1.20.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/oidc-token-hash/5.0.1:
|
||||||
|
resolution: {integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==}
|
||||||
|
engines: {node: ^10.13.0 || >=12.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/once/1.4.0:
|
/once/1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2860,6 +2929,15 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/openid-client/5.3.0:
|
||||||
|
resolution: {integrity: sha512-SykPCeZBZ/SxiBH5AWynvFUIDX3//2pgwc/3265alUmGHeCN03+X8uP+pHOVnCXCKfX/XOhO90qttAQ76XcGxA==}
|
||||||
|
dependencies:
|
||||||
|
jose: 4.11.0
|
||||||
|
lru-cache: 6.0.0
|
||||||
|
object-hash: 2.2.0
|
||||||
|
oidc-token-hash: 5.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/optionator/0.8.3:
|
/optionator/0.8.3:
|
||||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
@ -3053,6 +3131,19 @@ packages:
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/preact-render-to-string/5.2.6_preact@10.11.2:
|
||||||
|
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
|
||||||
|
peerDependencies:
|
||||||
|
preact: '>=10'
|
||||||
|
dependencies:
|
||||||
|
preact: 10.11.2
|
||||||
|
pretty-format: 3.8.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/preact/10.11.2:
|
||||||
|
resolution: {integrity: sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/prebuild-install/7.1.1:
|
/prebuild-install/7.1.1:
|
||||||
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
|
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -3133,6 +3224,10 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pretty-format/3.8.0:
|
||||||
|
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/pretty-ms/7.0.1:
|
/pretty-ms/7.0.1:
|
||||||
resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==}
|
resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -3148,13 +3243,13 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/prisma/4.6.0:
|
/prisma/4.6.1:
|
||||||
resolution: {integrity: sha512-TAnObUMGCM9NLt9nsRs1WWYQGPKsJOK8bN/7gSAnBcYIxMCFFDe+XtFYJbyTzsJZ/i+0rH4zg8au3m7HX354LA==}
|
resolution: {integrity: sha512-BR4itMCuzrDV4tn3e2TF+nh1zIX/RVU0isKtKoN28ADeoJ9nYaMhiuRRkFd2TZN8+l/XfYzoRKyHzUFXLQhmBQ==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/engines': 4.6.0
|
'@prisma/engines': 4.6.1
|
||||||
|
|
||||||
/process-nextick-args/2.0.1:
|
/process-nextick-args/2.0.1:
|
||||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||||
|
@ -3523,6 +3618,10 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 6.0.0
|
lru-cache: 6.0.0
|
||||||
|
|
||||||
|
/server-only/0.0.1:
|
||||||
|
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/set-blocking/2.0.0:
|
/set-blocking/2.0.0:
|
||||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -4024,6 +4123,11 @@ packages:
|
||||||
/util-deprecate/1.0.2:
|
/util-deprecate/1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
|
/uuid/8.3.2:
|
||||||
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/walkdir/0.4.1:
|
/walkdir/0.4.1:
|
||||||
resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==}
|
resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "AuthTokens" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"token" TEXT NOT NULL,
|
|
||||||
"expiredReason" TEXT NOT NULL,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
|
|
||||||
PRIMARY KEY ("id", "token")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "SequelizeMeta" (
|
|
||||||
"name" TEXT NOT NULL PRIMARY KEY
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Files" (
|
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"title" TEXT,
|
|
||||||
"content" TEXT,
|
|
||||||
"sha" TEXT,
|
|
||||||
"html" TEXT,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"postId" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "PostAuthors" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"postId" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
|
|
||||||
PRIMARY KEY ("id", "postId", "userId")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Posts" (
|
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"visibility" TEXT NOT NULL,
|
|
||||||
"password" TEXT,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME,
|
|
||||||
"expiresAt" DATETIME,
|
|
||||||
"parentId" TEXT,
|
|
||||||
"description" TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Users" (
|
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"username" TEXT NOT NULL,
|
|
||||||
"password" TEXT NOT NULL,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME,
|
|
||||||
"role" TEXT DEFAULT 'user',
|
|
||||||
"email" TEXT,
|
|
||||||
"displayName" TEXT,
|
|
||||||
"bio" TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "AuthTokens_id_token_key" ON "AuthTokens"("id", "token");
|
|
|
@ -1,19 +0,0 @@
|
||||||
-- RedefineTables
|
|
||||||
PRAGMA foreign_keys=OFF;
|
|
||||||
CREATE TABLE "new_AuthTokens" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"token" TEXT NOT NULL,
|
|
||||||
"expiredReason" TEXT,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
|
|
||||||
PRIMARY KEY ("id", "token")
|
|
||||||
);
|
|
||||||
INSERT INTO "new_AuthTokens" ("createdAt", "deletedAt", "expiredReason", "id", "token", "updatedAt", "userId") SELECT "createdAt", "deletedAt", "expiredReason", "id", "token", "updatedAt", "userId" FROM "AuthTokens";
|
|
||||||
DROP TABLE "AuthTokens";
|
|
||||||
ALTER TABLE "new_AuthTokens" RENAME TO "AuthTokens";
|
|
||||||
CREATE UNIQUE INDEX "AuthTokens_id_token_key" ON "AuthTokens"("id", "token");
|
|
||||||
PRAGMA foreign_key_check;
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- Made the column `content` on table `Files` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
- Made the column `html` on table `Files` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
- Made the column `sha` on table `Files` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
- Made the column `title` on table `Files` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- RedefineTables
|
|
||||||
PRAGMA foreign_keys=OFF;
|
|
||||||
CREATE TABLE "new_Files" (
|
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"content" TEXT NOT NULL,
|
|
||||||
"sha" TEXT NOT NULL,
|
|
||||||
"html" TEXT NOT NULL,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"postId" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
INSERT INTO "new_Files" ("content", "createdAt", "deletedAt", "html", "id", "postId", "sha", "title", "updatedAt", "userId") SELECT "content", "createdAt", "deletedAt", "html", "id", "postId", "sha", "title", "updatedAt", "userId" FROM "Files";
|
|
||||||
DROP TABLE "Files";
|
|
||||||
ALTER TABLE "new_Files" RENAME TO "Files";
|
|
||||||
PRAGMA foreign_key_check;
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- You are about to drop the `PostAuthors` table. If the table is not empty, all the data it contains will be lost.
|
|
||||||
- Added the required column `authorId` to the `Posts` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- DropTable
|
|
||||||
PRAGMA foreign_keys=off;
|
|
||||||
DROP TABLE "PostAuthors";
|
|
||||||
PRAGMA foreign_keys=on;
|
|
||||||
|
|
||||||
-- RedefineTables
|
|
||||||
PRAGMA foreign_keys=OFF;
|
|
||||||
CREATE TABLE "new_Posts" (
|
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"visibility" TEXT NOT NULL,
|
|
||||||
"password" TEXT,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL,
|
|
||||||
"deletedAt" DATETIME,
|
|
||||||
"expiresAt" DATETIME,
|
|
||||||
"parentId" TEXT,
|
|
||||||
"description" TEXT,
|
|
||||||
"authorId" TEXT NOT NULL
|
|
||||||
);
|
|
||||||
INSERT INTO "new_Posts" ("createdAt", "deletedAt", "description", "expiresAt", "id", "parentId", "password", "title", "updatedAt", "visibility") SELECT "createdAt", "deletedAt", "description", "expiresAt", "id", "parentId", "password", "title", "updatedAt", "visibility" FROM "Posts";
|
|
||||||
DROP TABLE "Posts";
|
|
||||||
ALTER TABLE "new_Posts" RENAME TO "Posts";
|
|
||||||
PRAGMA foreign_key_check;
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (i.e. Git)
|
|
||||||
provider = "sqlite"
|
|
|
@ -1,22 +1,24 @@
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
previewFeatures = ["referentialIntegrity"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "postgresql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
|
referentialIntegrity = "prisma"
|
||||||
}
|
}
|
||||||
|
|
||||||
model AuthTokens {
|
model AuthTokens {
|
||||||
id String @default(cuid())
|
id String @default(cuid())
|
||||||
token String
|
token String
|
||||||
expiredReason String?
|
expiredReason String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
userId String
|
userId String
|
||||||
// TODO: verify this isn't necessary / is replaced by an implicit m-n relation
|
// TODO: verify this isn't necessary / is replaced by an implicit m-n relation
|
||||||
// users User[] @relation(fields: [userId], references: [id], onDelete: Cascade)
|
// users DriftUser[] @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@id([id, token])
|
@@id([id, token])
|
||||||
// make id and token keys
|
// make id and token keys
|
||||||
|
@ -31,30 +33,16 @@ model File {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
title String
|
title String
|
||||||
content String
|
content String
|
||||||
sha String
|
sha String @unique
|
||||||
html String
|
html String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
userId String
|
userId String
|
||||||
postId String
|
postId String
|
||||||
// posts Post[] @relation(fields: [postId], references: [id], onDelete: Cascade)
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||||
// users User[] @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@map("Files")
|
@@map("files")
|
||||||
}
|
|
||||||
|
|
||||||
model PostToAuthors {
|
|
||||||
id String @default(cuid())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
postId String
|
|
||||||
userId String
|
|
||||||
// users User[] @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
// posts Post[] @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@id([id, postId, userId])
|
|
||||||
@@map("PostAuthors")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model Post {
|
model Post {
|
||||||
|
@ -68,25 +56,72 @@ model Post {
|
||||||
expiresAt DateTime?
|
expiresAt DateTime?
|
||||||
parentId String?
|
parentId String?
|
||||||
description String?
|
description String?
|
||||||
authorId String
|
author User? @relation(fields: [authorId], references: [id])
|
||||||
|
authorId String?
|
||||||
|
files File[]
|
||||||
|
|
||||||
@@map("Posts")
|
@@map("posts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next auth stuff, from https://next-auth.js.org/adapters/prisma
|
||||||
|
|
||||||
|
model Account {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
userId String
|
||||||
|
type String
|
||||||
|
provider String
|
||||||
|
providerAccountId String
|
||||||
|
refresh_token String? @db.Text
|
||||||
|
access_token String? @db.Text
|
||||||
|
expires_at Int?
|
||||||
|
token_type String?
|
||||||
|
scope String?
|
||||||
|
id_token String? @db.Text
|
||||||
|
session_state String?
|
||||||
|
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||||
|
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||||
|
// https://next-auth.js.org/providers/github
|
||||||
|
refresh_token_expires_in Int?
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([provider, providerAccountId])
|
||||||
|
@@map(name: "accounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionToken String @unique
|
||||||
|
userId String
|
||||||
|
expires DateTime
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
username String
|
name String?
|
||||||
password String
|
email String? @unique
|
||||||
createdAt DateTime @default(now())
|
emailVerified DateTime?
|
||||||
updatedAt DateTime @updatedAt
|
image String?
|
||||||
deletedAt DateTime?
|
|
||||||
role String? @default("user")
|
|
||||||
email String?
|
|
||||||
displayName String?
|
|
||||||
bio String?
|
|
||||||
// AuthTokens AuthTokens[]
|
|
||||||
// files File[]
|
|
||||||
// post_authors PostToAuthors[]
|
|
||||||
|
|
||||||
@@map("Users")
|
accounts Account[]
|
||||||
|
sessions Session[]
|
||||||
|
|
||||||
|
// custom fields
|
||||||
|
posts Post[]
|
||||||
|
username String? @unique
|
||||||
|
role String? @default("user")
|
||||||
|
password String? @db.Text
|
||||||
|
|
||||||
|
|
||||||
|
@@map("users")
|
||||||
|
}
|
||||||
|
|
||||||
|
model VerificationToken {
|
||||||
|
identifier String
|
||||||
|
token String @unique
|
||||||
|
expires DateTime
|
||||||
|
|
||||||
|
@@unique([identifier, token])
|
||||||
|
@@map("verification_tokens")
|
||||||
}
|
}
|
||||||
|
|
20
client/types/next-auth.d.ts
vendored
Normal file
20
client/types/next-auth.d.ts
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { User } from "next-auth"
|
||||||
|
import { JWT } from "next-auth/jwt"
|
||||||
|
|
||||||
|
type UserId = string
|
||||||
|
|
||||||
|
declare module "next-auth/jwt" {
|
||||||
|
interface JWT {
|
||||||
|
id: UserId
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "next-auth" {
|
||||||
|
interface Session {
|
||||||
|
user: User & {
|
||||||
|
id: UserId
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue