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 Header from "@components/header"
|
||||
|
||||
export default function SignInPage() {
|
||||
return <Auth page="signin" />
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Auth page="signin" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Auth from "@components/auth"
|
||||
import Header from "@components/header"
|
||||
import { getRequiresPasscode } from "pages/api/auth/requires-passcode"
|
||||
|
||||
const getPasscode = async () => {
|
||||
|
@ -7,5 +8,10 @@ const getPasscode = async () => {
|
|||
|
||||
export default async function SignUpPage() {
|
||||
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}
|
||||
height={48}
|
||||
alt=""
|
||||
priority
|
||||
/>
|
||||
</ShiftBy>
|
||||
<Spacer />
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Header from "@components/header"
|
||||
import { getCurrentUser } from "@lib/server/session"
|
||||
import { getWelcomeContent } from "pages/api/welcome"
|
||||
import Home from "./home"
|
||||
|
||||
|
@ -8,6 +10,12 @@ const getWelcomeData = async () => {
|
|||
|
||||
export default async function Page() {
|
||||
const { content, rendered, title } = await getWelcomeData()
|
||||
|
||||
return <Home rendered={rendered} introContent={content} introTitle={title} />
|
||||
const authed = await getCurrentUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header signedIn={Boolean(authed)}/>
|
||||
<Home rendered={rendered} introContent={content} introTitle={title} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import NewPost from "@components/new-post"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { cookies } from "next/headers"
|
||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||
import { getPostWithFiles } from "app/prisma"
|
||||
import { useRedirectIfNotAuthed } from "@lib/server/hooks/use-redirect-if-not-authed"
|
||||
import { getPostWithFiles } from "@lib/server/prisma"
|
||||
import Header from "@components/header"
|
||||
|
||||
const NewFromExisting = async ({
|
||||
params
|
||||
|
@ -14,13 +12,6 @@ const NewFromExisting = async ({
|
|||
}) => {
|
||||
const { id } = params
|
||||
const router = useRouter()
|
||||
const cookieList = cookies()
|
||||
useRedirectIfNotAuthed()
|
||||
const driftToken = cookieList.get(TOKEN_COOKIE_NAME)
|
||||
|
||||
if (!driftToken) {
|
||||
return router.push("/signin")
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
return router.push("/new")
|
||||
|
@ -28,7 +19,12 @@ const NewFromExisting = async ({
|
|||
|
||||
const post = await getPostWithFiles(id)
|
||||
|
||||
return <NewPost initialPost={post} newPostParent={id} />
|
||||
return (
|
||||
<>
|
||||
<Header signedIn />
|
||||
<NewPost initialPost={post} newPostParent={id} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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 }) {
|
||||
// useRedirectIfNotAuthed()
|
||||
const user = getCurrentUser()
|
||||
if (!user) {
|
||||
return redirect("/new")
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import Header from "@components/header"
|
||||
import NewPost from "@components/new-post"
|
||||
import "@styles/react-datepicker.css"
|
||||
|
||||
const New = () => <NewPost />
|
||||
const New = () => <>
|
||||
<Header signedIn />
|
||||
<NewPost />
|
||||
</>
|
||||
|
||||
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 { notFound, useRouter } from "next/navigation"
|
||||
import { notFound, redirect, useRouter } from "next/navigation"
|
||||
import { cookies } from "next/headers"
|
||||
import { getPostsByUser } from "app/prisma"
|
||||
import { getPostsByUser } from "@lib/server/prisma"
|
||||
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() {
|
||||
const userId = cookies().get(USER_COOKIE_NAME)?.value
|
||||
const userId = (await getCurrentUser())?.id
|
||||
|
||||
if (!userId) {
|
||||
return notFound()
|
||||
redirect(authOptions.pages?.signIn || "/new")
|
||||
}
|
||||
|
||||
const posts = await getPostsByUser(userId, true)
|
||||
|
||||
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 { 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 { notFound } from "next/navigation"
|
||||
|
||||
const AdminPage = async () => {
|
||||
const driftToken = cookies().get(TOKEN_COOKIE_NAME)?.value
|
||||
if (!driftToken) {
|
||||
const user = await getCurrentUser()
|
||||
|
||||
if (!user) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const isAdmin = await isUserAdmin(driftToken)
|
||||
|
||||
if (!isAdmin) {
|
||||
if (user.role !== "admin") {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import "@styles/globals.css"
|
||||
import { ServerThemeProvider } from "next-themes"
|
||||
import { LayoutWrapper } from "./root-layout-wrapper"
|
||||
import styles from '@styles/Home.module.css';
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: RootLayoutProps) {
|
||||
export default async function RootLayout({ children }: RootLayoutProps) {
|
||||
return (
|
||||
<ServerThemeProvider
|
||||
cookieName="drift-theme"
|
||||
// disableTransitionOnChange
|
||||
disableTransitionOnChange
|
||||
attribute="data-theme"
|
||||
enableColorScheme
|
||||
>
|
||||
|
@ -50,7 +51,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||
<meta name="theme-color" content="#ffffff" />
|
||||
<title>Drift</title>
|
||||
</head>
|
||||
<body>
|
||||
<body className={styles.main}>
|
||||
<LayoutWrapper>{children}</LayoutWrapper>
|
||||
</body>
|
||||
</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"
|
||||
|
||||
import Header from "@components/header"
|
||||
import { CssBaseline, GeistProvider, Page, Themes } from "@geist-ui/core/dist"
|
||||
import { ThemeProvider } from "next-themes"
|
||||
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 skeletonHighlightColor = "var(--lighter-gray)"
|
||||
|
||||
|
@ -30,7 +32,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||
dropdownBoxShadow: "0 0 0 1px var(--lighter-gray)",
|
||||
shadowSmall: "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: {
|
||||
gap: "var(--gap)",
|
||||
|
@ -59,12 +61,11 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||
attribute="data-theme"
|
||||
>
|
||||
<CssBaseline />
|
||||
<Page width={"100%"}>
|
||||
<Page.Header>
|
||||
<Header />
|
||||
</Page.Header>
|
||||
|
||||
<Page.Content className={styles.main}>{children}</Page.Content>
|
||||
<Page width={"100%"} style={{
|
||||
marginTop: "0 !important",
|
||||
paddingTop: "0 !important"
|
||||
}}>
|
||||
{children}
|
||||
</Page>
|
||||
</ThemeProvider>
|
||||
</SkeletonTheme>
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
flex-direction: column;
|
||||
place-items: center;
|
||||
gap: 10px;
|
||||
max-width: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.formContentSpace {
|
||||
|
|
|
@ -4,11 +4,9 @@ import { FormEvent, useState } from "react"
|
|||
import styles from "./auth.module.css"
|
||||
import { useRouter } from "next/navigation"
|
||||
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 { signIn } from "next-auth/react"
|
||||
import { Github as GithubIcon } from "@geist-ui/icons"
|
||||
const NO_EMPTY_SPACE_REGEX = /^\S*$/
|
||||
const ERROR_MESSAGE =
|
||||
"Provide a non empty username and a password with at least 6 characters"
|
||||
|
@ -27,29 +25,27 @@ const Auth = ({
|
|||
const [serverPassword, setServerPassword] = useState("")
|
||||
const [errorMsg, setErrorMsg] = useState("")
|
||||
const signingIn = page === "signin"
|
||||
const { signin } = useSignedIn()
|
||||
|
||||
const handleJson = (json: any) => {
|
||||
signin(json.token)
|
||||
setCookie(USER_COOKIE_NAME, json.userId)
|
||||
// setCookie(USER_COOKIE_NAME, json.userId)
|
||||
|
||||
router.push("/new")
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
if (
|
||||
!signingIn &&
|
||||
(!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)
|
||||
)
|
||||
return setErrorMsg(ERROR_MESSAGE)
|
||||
if (
|
||||
!signingIn &&
|
||||
requiresServerPassword &&
|
||||
!NO_EMPTY_SPACE_REGEX.test(serverPassword)
|
||||
)
|
||||
return setErrorMsg(ERROR_MESSAGE)
|
||||
else setErrorMsg("")
|
||||
// if (
|
||||
// !signingIn &&
|
||||
// (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)
|
||||
// )
|
||||
// return setErrorMsg(ERROR_MESSAGE)
|
||||
// if (
|
||||
// !signingIn &&
|
||||
// requiresServerPassword &&
|
||||
// !NO_EMPTY_SPACE_REGEX.test(serverPassword)
|
||||
// )
|
||||
// return setErrorMsg(ERROR_MESSAGE)
|
||||
// else setErrorMsg("")
|
||||
|
||||
const reqOpts = {
|
||||
method: "POST",
|
||||
|
@ -60,12 +56,18 @@ const Auth = ({
|
|||
}
|
||||
|
||||
try {
|
||||
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)
|
||||
// signIn("credentials", {
|
||||
// callbackUrl: "/new",
|
||||
// redirect: false,
|
||||
// username,
|
||||
// password,
|
||||
// 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) {
|
||||
setErrorMsg(err.message ?? "Something went wrong")
|
||||
}
|
||||
|
@ -77,9 +79,10 @@ const Auth = ({
|
|||
<div className={styles.formContentSpace}>
|
||||
<h1>{signingIn ? "Sign In" : "Sign Up"}</h1>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <form onSubmit={handleSubmit}> */}
|
||||
<form>
|
||||
<div className={styles.formGroup}>
|
||||
<Input
|
||||
{/* <Input
|
||||
htmlType="text"
|
||||
id="username"
|
||||
value={username}
|
||||
|
@ -87,6 +90,7 @@ const Auth = ({
|
|||
placeholder="Username"
|
||||
required
|
||||
minLength={3}
|
||||
width="100%"
|
||||
/>
|
||||
<Input
|
||||
htmlType="password"
|
||||
|
@ -96,7 +100,9 @@ const Auth = ({
|
|||
placeholder="Password"
|
||||
required
|
||||
minLength={6}
|
||||
/>
|
||||
width="100%"
|
||||
/> */}
|
||||
{/* sign in with github */}
|
||||
{requiresServerPassword && (
|
||||
<Input
|
||||
htmlType="password"
|
||||
|
@ -110,10 +116,20 @@ const Auth = ({
|
|||
width="100%"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button width={"100%"} htmlType="submit">
|
||||
{signingIn ? "Sign In" : "Sign Up"}
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="success-light"
|
||||
auto
|
||||
width="100%"
|
||||
icon={<GithubIcon />}
|
||||
onClick={() => signIn("github")}
|
||||
>
|
||||
Sign in with GitHub
|
||||
</Button>
|
||||
|
||||
{/* <Button width={"100%"} htmlType="submit">
|
||||
{signingIn ? "Sign In" : "Sign Up"}
|
||||
</Button> */}
|
||||
</div>
|
||||
<div className={styles.formContentSpace}>
|
||||
{signingIn ? (
|
||||
|
@ -125,7 +141,7 @@ const Auth = ({
|
|||
</p>
|
||||
) : (
|
||||
<p>
|
||||
Already have an account?{" "}
|
||||
Have an account?{" "}
|
||||
<Link colored href="/signin">
|
||||
Sign in
|
||||
</Link>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
.mobile {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.controls {
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import styles from "./header.module.css"
|
||||
import useSignedIn from "../../lib/hooks/use-signed-in"
|
||||
|
||||
import HomeIcon from "@geist-ui/icons/home"
|
||||
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 SunIcon from "@geist-ui/icons/sun"
|
||||
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 { usePathname } from "next/navigation"
|
||||
|
||||
|
@ -37,13 +36,13 @@ type Tab = {
|
|||
href?: string
|
||||
}
|
||||
|
||||
const Header = () => {
|
||||
const Header = ({ signedIn = false }) => {
|
||||
const pathname = usePathname()
|
||||
const [expanded, setExpanded] = useState<boolean>(false)
|
||||
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
|
||||
const isMobile = useMediaQuery("xs", { match: "down" })
|
||||
const { signedIn: isSignedIn } = useSignedIn()
|
||||
const userData = useUserData()
|
||||
// const { status } = useSession()
|
||||
// const signedIn = status === "authenticated"
|
||||
const [pages, setPages] = useState<Tab[]>([])
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
|
||||
|
@ -76,7 +75,7 @@ const Header = () => {
|
|||
}
|
||||
]
|
||||
|
||||
if (isSignedIn)
|
||||
if (signedIn)
|
||||
setPages([
|
||||
{
|
||||
name: "new",
|
||||
|
@ -126,20 +125,20 @@ const Header = () => {
|
|||
},
|
||||
...defaultPages
|
||||
])
|
||||
if (userData?.role === "admin") {
|
||||
setPages((pages) => [
|
||||
...pages,
|
||||
{
|
||||
name: "admin",
|
||||
icon: <SettingsIcon />,
|
||||
value: "admin",
|
||||
href: "/admin"
|
||||
}
|
||||
])
|
||||
}
|
||||
// if (userData?.role === "admin") {
|
||||
// setPages((pages) => [
|
||||
// ...pages,
|
||||
// {
|
||||
// name: "admin",
|
||||
// icon: <SettingsIcon />,
|
||||
// value: "admin",
|
||||
// href: "/admin"
|
||||
// }
|
||||
// ])
|
||||
// }
|
||||
// TODO: investigate deps causing infinite loop
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isMobile, isSignedIn, resolvedTheme, userData])
|
||||
}, [isMobile, resolvedTheme])
|
||||
|
||||
const onTabChange = useCallback(
|
||||
(tab: string) => {
|
||||
|
@ -172,7 +171,6 @@ const Header = () => {
|
|||
return (
|
||||
<Link key={tab.value} href={tab.href} className={styles.tab}>
|
||||
<Button
|
||||
className={activeStyle}
|
||||
auto={isMobile ? false : true}
|
||||
icon={tab.icon}
|
||||
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 getTitleForPostCopy from "@lib/get-title-for-post-copy"
|
||||
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 { getCookie } from "cookies-next"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from "react"
|
|||
|
||||
type PageSeoProps = {
|
||||
title?: string
|
||||
description?: string
|
||||
description?: string | null
|
||||
isLoading?: boolean
|
||||
isPrivate?: boolean
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import useDebounce from "@lib/hooks/use-debounce"
|
|||
import Link from "@components/link"
|
||||
import { TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||
import { getCookie } from "cookies-next"
|
||||
import type { PostWithFiles } from "app/prisma"
|
||||
import type { PostWithFiles } from "@lib/server/prisma"
|
||||
|
||||
type Props = {
|
||||
initialPosts: PostWithFiles[]
|
||||
|
|
|
@ -17,7 +17,7 @@ import { useRouter } from "next/router"
|
|||
import Parent from "@geist-ui/icons/arrowUpCircle"
|
||||
import styles from "./list-item.module.css"
|
||||
import Link from "@components/link"
|
||||
import { PostWithFiles, File } from "app/prisma"
|
||||
import { PostWithFiles, File } from "@lib/server/prisma"
|
||||
import { PostVisibility } from "@lib/types"
|
||||
|
||||
// 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
|
||||
|
||||
return (
|
||||
<Page width={"100%"}>
|
||||
<PageSeo
|
||||
title={`${post.title} - Drift`}
|
||||
description={post.description}
|
||||
isPrivate={false}
|
||||
/>
|
||||
<>
|
||||
|
||||
{!isAvailable && <PasswordModalPage setPost={setPost} />}
|
||||
<Page.Content className={homeStyles.main}>
|
||||
<div className={styles.header}>
|
||||
|
@ -178,7 +174,7 @@ const PostPage = ({ post: initialPost, isProtected }: Props) => {
|
|||
)}
|
||||
<ScrollToTop />
|
||||
</Page.Content>
|
||||
</Page>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
'use client';
|
||||
import { Fieldset, Text, Divider } from "@geist-ui/core/dist"
|
||||
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 { TOKEN_COOKIE_NAME } from "@lib/constants"
|
||||
import useUserData from "@lib/hooks/use-user-data"
|
||||
import { getCookie } from "cookies-next"
|
||||
import { User } from "next-auth"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
const Profile = () => {
|
||||
const user = useUserData()
|
||||
const Profile = ({ user }: { user: User }) => {
|
||||
const [name, setName] = useState<string>()
|
||||
const [email, setEmail] = useState<string>()
|
||||
const [bio, setBio] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
console.log(user)
|
||||
if (user?.displayName) setName(user.displayName)
|
||||
// if (user?.displayName) setName(user.displayName)
|
||||
if (user?.email) setEmail(user.email)
|
||||
if (user?.bio) setBio(user.bio)
|
||||
// if (user?.bio) setBio(user.bio)
|
||||
}, [user])
|
||||
|
||||
const { setToast } = useToasts()
|
||||
|
|
|
@ -10,6 +10,8 @@ type Config = {
|
|||
welcome_content: string
|
||||
welcome_title: string
|
||||
url: string
|
||||
GITHUB_CLIENT_ID: string
|
||||
GITHUB_CLIENT_SECRET: string
|
||||
}
|
||||
|
||||
type EnvironmentValue = string | undefined
|
||||
|
@ -80,7 +82,9 @@ export const config = (env: Environment): Config => {
|
|||
registration_password: env.REGISTRATION_PASSWORD ?? "",
|
||||
welcome_content: env.WELCOME_CONTENT ?? "",
|
||||
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
|
||||
}
|
||||
|
|
|
@ -17,13 +17,13 @@ const useSignedIn = () => {
|
|||
setCookie(TOKEN_COOKIE_NAME, token)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
setSignedIn(true)
|
||||
} else {
|
||||
setSignedIn(false)
|
||||
}
|
||||
}, [setSignedIn, token])
|
||||
// useEffect(() => {
|
||||
// if (token) {
|
||||
// setSignedIn(true)
|
||||
// } else {
|
||||
// setSignedIn(false)
|
||||
// }
|
||||
// }, [setSignedIn, token])
|
||||
|
||||
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 { User } from "@prisma/client"
|
||||
import prisma from "app/prisma"
|
||||
import prisma from "@lib/server/prisma"
|
||||
import { sign } from "jsonwebtoken"
|
||||
|
||||
export async function generateAndExpireAccessToken(userId: User["id"]) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -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 { User } from "@prisma/client"
|
||||
import prisma from "app/prisma"
|
||||
import prisma from "@lib/server/prisma"
|
||||
import * as jwt from "jsonwebtoken"
|
||||
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 } from "app/prisma"
|
||||
import { User } from "@lib/server/prisma"
|
||||
import { setCookie } from "cookies-next"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import { generateAndExpireAccessToken } from "./generate-access-token"
|
||||
|
|
|
@ -1,68 +1,38 @@
|
|||
import { NextFetchEvent, NextResponse } from "next/server"
|
||||
import type { NextRequest } from "next/server"
|
||||
import { TOKEN_COOKIE_NAME, USER_COOKIE_NAME } from "@lib/constants"
|
||||
import serverConfig from "@lib/config"
|
||||
import { getToken } from "next-auth/jwt"
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
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) {
|
||||
const pathname = req.nextUrl.pathname
|
||||
const signedIn = req.cookies.get(TOKEN_COOKIE_NAME)
|
||||
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 (isAuthPage) {
|
||||
if (isAuth) {
|
||||
return NextResponse.redirect(new URL("/new", req.url))
|
||||
}
|
||||
|
||||
if (!req.headers.get("x-middleware-preflight") && pathname === "/signout") {
|
||||
// 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
|
||||
return null
|
||||
}
|
||||
} 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/")) {
|
||||
const urlWithoutVisibility = pathname
|
||||
.replace("/protected/", "/")
|
||||
.replace("/private/", "/")
|
||||
.substring(1)
|
||||
return NextResponse.redirect(getURL(urlWithoutVisibility))
|
||||
if (!isAuth) {
|
||||
return NextResponse.redirect(new URL("/signin", req.url))
|
||||
}
|
||||
},
|
||||
{
|
||||
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 = {
|
||||
match: [
|
||||
|
@ -71,9 +41,6 @@ export const config = {
|
|||
"/signin",
|
||||
"/signup",
|
||||
"/new",
|
||||
"/protected/:path*",
|
||||
"/private/:path*"
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -14,7 +14,8 @@
|
|||
"dependencies": {
|
||||
"@geist-ui/core": "^2.3.8",
|
||||
"@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",
|
||||
"client-zip": "2.2.1",
|
||||
"clsx": "^1.2.1",
|
||||
|
@ -22,7 +23,8 @@
|
|||
"dotenv": "16.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"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",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"rc-table": "7.24.1",
|
||||
|
@ -32,6 +34,7 @@
|
|||
"react-dropzone": "14.2.3",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-loading-skeleton": "3.1.0",
|
||||
"server-only": "^0.0.1",
|
||||
"showdown": "^2.1.0",
|
||||
"swr": "1.3.0",
|
||||
"textarea-markdown-editor": "0.1.13",
|
||||
|
@ -52,7 +55,7 @@
|
|||
"eslint-config-next": "13.0.2",
|
||||
"next-unused": "0.0.6",
|
||||
"prettier": "2.6.2",
|
||||
"prisma": "^4.6.0",
|
||||
"prisma": "^4.6.1",
|
||||
"typescript": "4.6.4",
|
||||
"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 prisma from "app/prisma"
|
||||
import prisma from "@lib/server/prisma"
|
||||
import bcrypt from "bcrypt"
|
||||
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(
|
||||
req: NextApiRequest,
|
||||
|
@ -18,7 +20,7 @@ export default async function handler(
|
|||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
if (!user || !user.password) {
|
||||
return res.status(401).json({ error: "Unauthorized" })
|
||||
}
|
||||
|
||||
|
@ -27,7 +29,23 @@ export default async function handler(
|
|||
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 })
|
||||
}
|
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 { parseQueryParam } from "@lib/server/parse-query-param"
|
||||
import prisma from "app/prisma"
|
||||
import prisma from "@lib/server/prisma"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export default async function handler(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { parseQueryParam } from "@lib/server/parse-query-param"
|
||||
import { getPostsByUser } from "app/prisma"
|
||||
import { getPostsByUser } from "@lib/server/prisma"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export default async function handle(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// })
|
||||
|
||||
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 { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ lockfileVersion: 5.4
|
|||
specifiers:
|
||||
'@geist-ui/core': ^2.3.8
|
||||
'@geist-ui/icons': 1.0.2
|
||||
'@next-auth/prisma-adapter': ^1.0.5
|
||||
'@next/bundle-analyzer': 12.1.6
|
||||
'@prisma/client': ^4.6.0
|
||||
'@prisma/client': ^4.6.1
|
||||
'@types/bcrypt': ^5.0.0
|
||||
'@types/jsonwebtoken': ^8.5.9
|
||||
'@types/marked': ^4.0.7
|
||||
|
@ -23,12 +24,13 @@ specifiers:
|
|||
eslint-config-next: 13.0.2
|
||||
jsonwebtoken: ^8.5.1
|
||||
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-unused: 0.0.6
|
||||
prettier: 2.6.2
|
||||
prism-react-renderer: ^1.3.5
|
||||
prisma: ^4.6.0
|
||||
prisma: ^4.6.1
|
||||
rc-table: 7.24.1
|
||||
react: 18.2.0
|
||||
react-datepicker: 4.8.0
|
||||
|
@ -36,6 +38,7 @@ specifiers:
|
|||
react-dropzone: 14.2.3
|
||||
react-hot-toast: ^2.4.0
|
||||
react-loading-skeleton: 3.1.0
|
||||
server-only: ^0.0.1
|
||||
sharp: ^0.31.2
|
||||
showdown: ^2.1.0
|
||||
swr: 1.3.0
|
||||
|
@ -47,7 +50,8 @@ specifiers:
|
|||
dependencies:
|
||||
'@geist-ui/core': 2.3.8_biqbaboplfbrettd7655fr4n2y
|
||||
'@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
|
||||
client-zip: 2.2.1
|
||||
clsx: 1.2.1
|
||||
|
@ -55,8 +59,9 @@ dependencies:
|
|||
dotenv: 16.0.0
|
||||
jsonwebtoken: 8.5.1
|
||||
marked: 4.2.2
|
||||
next: 13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y
|
||||
next-themes: /@wits/next-themes/0.2.7_qjr36eup74ongf7bl2iopfchwe
|
||||
next: 13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y
|
||||
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
|
||||
rc-table: 7.24.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
|
@ -65,6 +70,7 @@ dependencies:
|
|||
react-dropzone: 14.2.3_react@18.2.0
|
||||
react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y
|
||||
react-loading-skeleton: 3.1.0_react@18.2.0
|
||||
server-only: 0.0.1
|
||||
showdown: 2.1.0
|
||||
swr: 1.3.0_react@18.2.0
|
||||
textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y
|
||||
|
@ -88,7 +94,7 @@ devDependencies:
|
|||
eslint-config-next: 13.0.2_hsmo2rtalirsvadpuxki35bq2i
|
||||
next-unused: 0.0.6
|
||||
prettier: 2.6.2
|
||||
prisma: 4.6.0
|
||||
prisma: 4.6.1
|
||||
typescript: 4.6.4
|
||||
typescript-plugin-css-modules: 3.4.0_typescript@4.6.4
|
||||
|
||||
|
@ -211,6 +217,16 @@ packages:
|
|||
- supports-color
|
||||
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:
|
||||
resolution: {integrity: sha512-WLydwytAeHoC/neXsiIgK+a6Me12PuSpwopnsZgX5JFNwXQ9MlwPeMGS3aTZkYsv8QmSm0Ns9Yh9FkgLKYaUuQ==}
|
||||
dependencies:
|
||||
|
@ -220,8 +236,8 @@ packages:
|
|||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@next/env/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-Ugn4VxB+2Bd1LnWcMbjIwNcVYPoBZ8Yo6j2A3MU99pzeYq+TGtHcYPz0xyIAP3Qp7mrH5gx6PITVz7D22u8p7w==}
|
||||
/@next/env/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-IKMYPznB0ttgHa1K7nKbfSMM8kne3G7Am+eNeM11cr+HjPljAzl863Ib9UBk6s7oChTAEVtaoKHbAerW/36tWA==}
|
||||
dev: false
|
||||
|
||||
/@next/eslint-plugin-next/13.0.2:
|
||||
|
@ -230,8 +246,8 @@ packages:
|
|||
glob: 7.1.7
|
||||
dev: true
|
||||
|
||||
/@next/swc-android-arm-eabi/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-ZZG0C+P4czfq5Zyhdouacb3w73w/iOj4KidWCpWlYfTnxlMinPoEDk04xFg5iR665ePlS2mrBnj2OfhckYcFdQ==}
|
||||
/@next/swc-android-arm-eabi/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-3CXPHZfP7KGwKlrBv451x3l++q1Jxr/5PESk1TkFednJmw+9F6Tno+2RPYEzE++EWxjuAM8SmwHZxhJ6HorOvA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
@ -239,8 +255,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-0Nw4n6Eox1cCp0d9BJ5GQDgW2+8JxoF5asdOdN0E1a6ayygOfsXN/GP3VWcrpLSrx6K1XUO+lgBbCbaOjvnoxA==}
|
||||
/@next/swc-android-arm64/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-hjsSok+41ZYDghIXMUrvv1eyDboinpDu5kcd/aQTqiV9ukuoQSQFwPd9i8fXVWKOb8w9rfoSLrPoslZXxbMolw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
@ -248,8 +264,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-TkSQVEEcmCfbzotHNHGWe1PkiZZkKPg4QWylZYv8UDfRUwJwR94aJeriOqlGOTkKQ/6a+ulJrVgs50/5gTTIHg==}
|
||||
/@next/swc-darwin-arm64/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-DxpeUXj7UcSidRDH0WjDzFlrycNvCKtQgpjPEzljBs2MGXGisuJ/znFkmqbLYwUi71La0nw91Yuz7IrGDpbhag==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
@ -257,8 +273,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-hGCarEZsaSdOWtJOUJc4Sr3oRzUjlI/G+qlyMkaceSTyYx4Xu2/OmDS1fCWxoltlimiHmlJpLnGGaxUgrZ8dkQ==}
|
||||
/@next/swc-darwin-x64/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-jGdLe9QRpbSMkO+Ttpr8fnl2q/s1cQuBvGKM0nHiIUtwuwnho4BjcYQdcCJbjjH2Vs0KMhayZh9REa+52vdAEA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
@ -266,8 +282,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-R7WFI/whtuSB6gxmzgqFzeKbrhuSp3ut0GaQK+kvb7NUnFe9xABUksdxEU8bORjVJaADgDsCsCHSsHGqHHl7Mg==}
|
||||
/@next/swc-freebsd-x64/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-9VJCLOkbteSozo8kxrqiFJDntARLIn0Uv4aXdvbAuYhEIVRbnP0uA3z1r6d4g8ycC1Yout6z0m3pkg0MHbKV2w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
@ -275,8 +291,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-RzYf+MTdP8Rvz/fijlxsTP+1S24ziMtCtzq2Ui8Qjg7VIfD9sEuLmMQJpm0k/FscduQdZILoG+QNhD2oW893Wg==}
|
||||
/@next/swc-linux-arm-gnueabihf/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-SBA6Ja07guZI8KnIpMRN6tDvD6tse70c8d9HPwdkK7JziwIBzNDSuLbuA9WB+9/byM70U8jROBKgMUZAsAbnew==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
@ -284,8 +300,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-8TF9UxIAZuQNf4fkyfZ1LcrqqvRI2Li0V2IO0CiCx4wg6xDqBjMH3IZoRwgY3yJ8UxdrFWf8Ec1q2WBYXcODgQ==}
|
||||
/@next/swc-linux-arm64-gnu/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-9hQU3mZtzuLAvqaz/72jM2IWtV3lcLFhWqWGCS8yqUCKjkT2ppd/L/VEVuvatC67H5wzpbAJPnDxjPIl7ryiOA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -293,8 +309,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-Ll2nV3pbCi3qL9o+6zxEuQAqqk8yPLk1TJ7+G8fTmm1vpjMjdV8eBiXiZVGyweRBhurhHmeSdh9JtpUFuPvDRA==}
|
||||
/@next/swc-linux-arm64-musl/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-iZTyAMbQiI0kng46mVp9XKscv59STqLbIVs6pSD3pnrBqKUh4SECQ6Z2r6Y4/H65ig64x6hvdk3KbG71UU+Kaw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
@ -302,8 +318,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-TARNMLz9+Ab2rEiuk/ulYULLDWw6zMc4yH2vFXdwckod9tWUyxptAMUz2umtKwyf6lmYUv4+IfZPJgUs0lr5Bw==}
|
||||
/@next/swc-linux-x64-gnu/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-2yYi/bjxf5jHJPTvnC6WbomgETkLWaNY+CEC2Ci1HV3xNVm1/4LiKB0KoDZGUWMBDjAQHO9LmTZS8+P4Q/wubA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -311,8 +327,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-Wbd1Ufm9NRSf+xl9kOfe5St06xHN1DHT0KrQc+cT2QKn9ZavASM/Vu2PM3gt4T/2Gqdv663WdbpEuX97wn3abQ==}
|
||||
/@next/swc-linux-x64-musl/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-DuT/jlTSyZDMPWDWpVqxkLJqGytXYnbIbZ8T+XRbOihDy8p4HwaKZW9ZcHM04lSnOmxwXFHRR5Exx4y5cQOH+A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
@ -320,8 +336,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-d/SiJzQvm+ggFhCBly4VuOUio0OXx5NKLabSw9AcxEK11/V6YGEFNVdPw1q059/eBi3S0mlRBBnowKuJiWGbtg==}
|
||||
/@next/swc-win32-arm64-msvc/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-TW1wLzOorp0IhBf2u1XiJ+8OmGWSUID8zWISwyW74oWuNIhpvzbgmCbjFqlfX9xUxAY6tVcx2TfOc5lmsIoaEg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
@ -329,8 +345,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-HytAShDnSnY1FkCpsy+t2V09H1Z9ydeZeg8QrLwub26bPWAcDZe77ECVR4rdIHqP4KHBwtAOM8UIZWrexlLggw==}
|
||||
/@next/swc-win32-ia32-msvc/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-cCbuBq8ua9u/bpJ0TvyTrEZXNhrzR0R0z/h3gitw+8VUQG4xREwfn3od0J9XjeL0RQ4QbtgorVE2yw9JZ5pOdg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
@ -338,8 +354,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/13.0.3-canary.2:
|
||||
resolution: {integrity: sha512-TPH7wQSLXbeWuwkGFASMkCmE2Q7Tt/S8gTOgC0Y4rJf1yw5K+YtubTZKmmEZ13Aq+fQtqg3NkPO9Rrq4OZpuGw==}
|
||||
/@next/swc-win32-x64-msvc/13.0.3-canary.4:
|
||||
resolution: {integrity: sha512-kWfN2WhqxwkaySEddUjm2xJKKdeaIE1/UZXFNCaU5aSZFTn1I4yVjjL40tMCfcppqYbY58X6c5UocOviLcKbrg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
@ -368,6 +384,10 @@ packages:
|
|||
fastq: 1.13.0
|
||||
dev: true
|
||||
|
||||
/@panva/hkdf/1.0.2:
|
||||
resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==}
|
||||
dev: false
|
||||
|
||||
/@polka/url/1.0.0-next.21:
|
||||
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
||||
dev: true
|
||||
|
@ -375,8 +395,8 @@ packages:
|
|||
/@popperjs/core/2.11.6:
|
||||
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
||||
|
||||
/@prisma/client/4.6.0_prisma@4.6.0:
|
||||
resolution: {integrity: sha512-D9LaQinDxOHinRpcJTw2tjMtjhc9HTP+aF1IRd2oLldp/8TiwIfxK8x17OhBBiX4y1PzbJXXET7kS+5wB3es/w==}
|
||||
/@prisma/client/4.6.1_prisma@4.6.1:
|
||||
resolution: {integrity: sha512-M1+NNrMzqaOIxT7PBGcTs3IZo7d1EW/+gVQd4C4gUgWBDGgD9AcIeZnUSidgWClmpMSgVUdnVORjsWWGUameYA==}
|
||||
engines: {node: '>=14.17'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
|
@ -385,16 +405,16 @@ packages:
|
|||
prisma:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@prisma/engines-version': 4.6.0-53.2e719efb80b56a3f32d18a62489de95bb9c130e3
|
||||
prisma: 4.6.0
|
||||
'@prisma/engines-version': 4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32
|
||||
prisma: 4.6.1
|
||||
dev: false
|
||||
|
||||
/@prisma/engines-version/4.6.0-53.2e719efb80b56a3f32d18a62489de95bb9c130e3:
|
||||
resolution: {integrity: sha512-0CTnfEuUbLlO6n1fM89ERDbSwI4LoyZn+1OKVSwG+aVqohj34+mXRfwOWIM0ONtYtLGGBpddvQAnAZkg+cgS6g==}
|
||||
/@prisma/engines-version/4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32:
|
||||
resolution: {integrity: sha512-HUCmkXAU2jqp2O1RvNtbE+seLGLyJGEABZS/R38rZjSAafAy0WzBuHq+tbZMnD+b5OSCsTVtIPVcuvx1ySxcWQ==}
|
||||
dev: false
|
||||
|
||||
/@prisma/engines/4.6.0:
|
||||
resolution: {integrity: sha512-S+72PAl0zTCbIGou1uXD/McvzdtP+bjOs0LRmGZfcOQcVqR9x/0f6Z+dqpUU0zIcqHEl+0DOB8UXaTwRvssFsQ==}
|
||||
/@prisma/engines/4.6.1:
|
||||
resolution: {integrity: sha512-3u2/XxvxB+Q7cMXHnKU0CpBiUK1QWqpgiBv28YDo1zOIJE3FCF8DI2vrp6vuwjGt5h0JGXDSvmSf4D4maVjJdw==}
|
||||
requiresBuild: true
|
||||
|
||||
/@rushstack/eslint-patch/1.2.0:
|
||||
|
@ -573,14 +593,14 @@ packages:
|
|||
eslint-visitor-keys: 3.3.0
|
||||
dev: true
|
||||
|
||||
/@wits/next-themes/0.2.7_qjr36eup74ongf7bl2iopfchwe:
|
||||
/@wits/next-themes/0.2.7_hsmqkug4agizydugca45idewda:
|
||||
resolution: {integrity: sha512-CpmNH3RRqf2w0i1Xbrz5GKNE/d5gMq1oBlGpofY9LWcjH225nUgrxP15wKRITRAbn68ERDbsBGEBiaRECTmQag==}
|
||||
peerDependencies:
|
||||
next: '*'
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
next: 13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y
|
||||
next: 13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
@ -973,6 +993,11 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cookie/0.5.0:
|
||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cookies-next/2.1.1:
|
||||
resolution: {integrity: sha512-AZGZPdL1hU3jCjN2UMJTGhLOYzNUN9Gm+v8BdptYIHUdwz397Et1p+sZRfvAl8pKnnmMdX2Pk9xDRKCGBum6GA==}
|
||||
dependencies:
|
||||
|
@ -2271,6 +2296,10 @@ packages:
|
|||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
dev: true
|
||||
|
||||
/jose/4.11.0:
|
||||
resolution: {integrity: sha512-wLe+lJHeG8Xt6uEubS4x0LVjS/3kXXu9dGoj9BNnlhYq7Kts0Pbb2pvv5KiI0yaKH/eaiR0LUOBhOVo9ktd05A==}
|
||||
dev: false
|
||||
|
||||
/js-sdsl/4.1.5:
|
||||
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
|
||||
dev: true
|
||||
|
@ -2677,6 +2706,32 @@ packages:
|
|||
dev: 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:
|
||||
resolution: {integrity: sha512-dHFNNBanFq4wvYrULtsjfWyZ6BzOnr5VYI9EYMGAZYF2vkAhFpj2JOuT5Wu2o3LbFSG92PmAZnSUF/LstF82pA==}
|
||||
hasBin: true
|
||||
|
@ -2688,8 +2743,8 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/next/13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-Qr19ElEa+ljqu56t4AoiZ6uld7jvMa9KbDFhXBcKQQ4/DaRGvLsoWDw9l3QADBhsFSegAon0NE7eI1IAP+M1pQ==}
|
||||
/next/13.0.3-canary.4_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-GCf0loggwGvPXeDfYMtg36HByukmALnldQZMIdQnGcJtFHRQsWrprvrTEfqTENU5UOZSYbTdJRdL1Y8QOyymWw==}
|
||||
engines: {node: '>=14.6.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
@ -2706,7 +2761,7 @@ packages:
|
|||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 13.0.3-canary.2
|
||||
'@next/env': 13.0.3-canary.4
|
||||
'@swc/helpers': 0.4.11
|
||||
caniuse-lite: 1.0.30001431
|
||||
postcss: 8.4.14
|
||||
|
@ -2715,19 +2770,19 @@ packages:
|
|||
styled-jsx: 5.1.0_react@18.2.0
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': 13.0.3-canary.2
|
||||
'@next/swc-android-arm64': 13.0.3-canary.2
|
||||
'@next/swc-darwin-arm64': 13.0.3-canary.2
|
||||
'@next/swc-darwin-x64': 13.0.3-canary.2
|
||||
'@next/swc-freebsd-x64': 13.0.3-canary.2
|
||||
'@next/swc-linux-arm-gnueabihf': 13.0.3-canary.2
|
||||
'@next/swc-linux-arm64-gnu': 13.0.3-canary.2
|
||||
'@next/swc-linux-arm64-musl': 13.0.3-canary.2
|
||||
'@next/swc-linux-x64-gnu': 13.0.3-canary.2
|
||||
'@next/swc-linux-x64-musl': 13.0.3-canary.2
|
||||
'@next/swc-win32-arm64-msvc': 13.0.3-canary.2
|
||||
'@next/swc-win32-ia32-msvc': 13.0.3-canary.2
|
||||
'@next/swc-win32-x64-msvc': 13.0.3-canary.2
|
||||
'@next/swc-android-arm-eabi': 13.0.3-canary.4
|
||||
'@next/swc-android-arm64': 13.0.3-canary.4
|
||||
'@next/swc-darwin-arm64': 13.0.3-canary.4
|
||||
'@next/swc-darwin-x64': 13.0.3-canary.4
|
||||
'@next/swc-freebsd-x64': 13.0.3-canary.4
|
||||
'@next/swc-linux-arm-gnueabihf': 13.0.3-canary.4
|
||||
'@next/swc-linux-arm64-gnu': 13.0.3-canary.4
|
||||
'@next/swc-linux-arm64-musl': 13.0.3-canary.4
|
||||
'@next/swc-linux-x64-gnu': 13.0.3-canary.4
|
||||
'@next/swc-linux-x64-musl': 13.0.3-canary.4
|
||||
'@next/swc-win32-arm64-msvc': 13.0.3-canary.4
|
||||
'@next/swc-win32-ia32-msvc': 13.0.3-canary.4
|
||||
'@next/swc-win32-x64-msvc': 13.0.3-canary.4
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
@ -2786,10 +2841,19 @@ packages:
|
|||
set-blocking: 2.0.0
|
||||
dev: false
|
||||
|
||||
/oauth/0.9.15:
|
||||
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
||||
dev: false
|
||||
|
||||
/object-assign/4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
|
||||
dev: true
|
||||
|
@ -2843,6 +2907,11 @@ packages:
|
|||
es-abstract: 1.20.4
|
||||
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:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
|
@ -2860,6 +2929,15 @@ packages:
|
|||
hasBin: 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:
|
||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
@ -3053,6 +3131,19 @@ packages:
|
|||
source-map-js: 1.0.2
|
||||
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:
|
||||
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -3133,6 +3224,10 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/pretty-format/3.8.0:
|
||||
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
|
||||
dev: false
|
||||
|
||||
/pretty-ms/7.0.1:
|
||||
resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -3148,13 +3243,13 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/prisma/4.6.0:
|
||||
resolution: {integrity: sha512-TAnObUMGCM9NLt9nsRs1WWYQGPKsJOK8bN/7gSAnBcYIxMCFFDe+XtFYJbyTzsJZ/i+0rH4zg8au3m7HX354LA==}
|
||||
/prisma/4.6.1:
|
||||
resolution: {integrity: sha512-BR4itMCuzrDV4tn3e2TF+nh1zIX/RVU0isKtKoN28ADeoJ9nYaMhiuRRkFd2TZN8+l/XfYzoRKyHzUFXLQhmBQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@prisma/engines': 4.6.0
|
||||
'@prisma/engines': 4.6.1
|
||||
|
||||
/process-nextick-args/2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
@ -3523,6 +3618,10 @@ packages:
|
|||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
/server-only/0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
dev: false
|
||||
|
||||
/set-blocking/2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
dev: false
|
||||
|
@ -4024,6 +4123,11 @@ packages:
|
|||
/util-deprecate/1.0.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==}
|
||||
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 {
|
||||
provider = "prisma-client-js"
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["referentialIntegrity"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
referentialIntegrity = "prisma"
|
||||
}
|
||||
|
||||
model AuthTokens {
|
||||
id String @default(cuid())
|
||||
id String @default(cuid())
|
||||
token String
|
||||
expiredReason String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
userId String
|
||||
// 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])
|
||||
// make id and token keys
|
||||
|
@ -31,30 +33,16 @@ model File {
|
|||
id String @id @default(cuid())
|
||||
title String
|
||||
content String
|
||||
sha String
|
||||
sha String @unique
|
||||
html String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
userId String
|
||||
postId String
|
||||
// posts Post[] @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
// users User[] @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@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")
|
||||
@@map("files")
|
||||
}
|
||||
|
||||
model Post {
|
||||
|
@ -68,25 +56,72 @@ model Post {
|
|||
expiresAt DateTime?
|
||||
parentId 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 {
|
||||
id String @id @default(cuid())
|
||||
username String
|
||||
password String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
role String? @default("user")
|
||||
email String?
|
||||
displayName String?
|
||||
bio String?
|
||||
// AuthTokens AuthTokens[]
|
||||
// files File[]
|
||||
// post_authors PostToAuthors[]
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
|
||||
@@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