start api transition, prisma additions
This commit is contained in:
parent
da870d6957
commit
9b9c3c1d87
82 changed files with 1671 additions and 202 deletions
|
@ -1,2 +0,0 @@
|
|||
API_URL=http://localhost:3000
|
||||
SECRET_KEY=secret
|
5
client/app/(auth)/head.tsx
Normal file
5
client/app/(auth)/head.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import PageSeo from "@components/head"
|
||||
|
||||
export default function AuthHead() {
|
||||
return <PageSeo title="Sign In" />
|
||||
}
|
5
client/app/(auth)/signin/page.tsx
Normal file
5
client/app/(auth)/signin/page.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Auth from "@components/auth"
|
||||
|
||||
export default function SignInPage() {
|
||||
return <Auth page="signin" />
|
||||
}
|
12
client/app/(auth)/signup/page.tsx
Normal file
12
client/app/(auth)/signup/page.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import Auth from "@components/auth"
|
||||
import config from "@lib/config"
|
||||
import { getRequiresPasscode } from "pages/api/auth/requires-passcode"
|
||||
|
||||
const getPasscode = async () => {
|
||||
return await getRequiresPasscode()
|
||||
}
|
||||
|
||||
export default async function SignUpPage() {
|
||||
const requiresPasscode = await getPasscode()
|
||||
return <Auth page="signup" requiresServerPassword={requiresPasscode} />
|
||||
}
|
67
client/app/(posts)/new/from/[id]/page.tsx
Normal file
67
client/app/(posts)/new/from/[id]/page.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import styles from "@styles/Home.module.css"
|
||||
import NewPost from "@components/new-post"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import Head from "next/head"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { Post } from "@lib/types"
|
||||
import cookie from "cookie"
|
||||
|
||||
|
||||
const NewFromExisting = async () => {
|
||||
return (
|
||||
// <Head>
|
||||
// {/* TODO: solve this. */}
|
||||
// {/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
// <link rel="stylesheet" href="/css/react-datepicker.css" />
|
||||
// </Head>
|
||||
<NewPost initialPost={post} newPostParent={parentId} />
|
||||
)
|
||||
}
|
||||
|
||||
// export const getServerSideProps: GetServerSideProps = async ({
|
||||
// req,
|
||||
// params
|
||||
// }) => {
|
||||
// const id = params?.id
|
||||
// const redirect = {
|
||||
// redirect: {
|
||||
// destination: "/new",
|
||||
// permanent: false
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!id) {
|
||||
// return redirect
|
||||
// }
|
||||
|
||||
// const driftToken = cookie.parse(req.headers.cookie || "")[`drift-token`]
|
||||
|
||||
// const post = await fetch(`${process.env.API_URL}/posts/${id}`, {
|
||||
// method: "GET",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// Authorization: `Bearer ${driftToken}`,
|
||||
// "x-secret-key": process.env.SECRET_KEY || ""
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (!post.ok) {
|
||||
// return redirect
|
||||
// }
|
||||
|
||||
// const data = await post.json()
|
||||
|
||||
// if (!data) {
|
||||
// return redirect
|
||||
// }
|
||||
|
||||
// return {
|
||||
// props: {
|
||||
// post: data,
|
||||
// parentId: id
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
export default NewFromExisting
|
5
client/app/(posts)/new/head.tsx
Normal file
5
client/app/(posts)/new/head.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import PageSeo from "@components/page-seo"
|
||||
|
||||
export default function NewPostHead() {
|
||||
return <PageSeo title="Create a new Drift" />
|
||||
}
|
10
client/app/(posts)/new/page.tsx
Normal file
10
client/app/(posts)/new/page.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import NewPost from "@components/new-post"
|
||||
import '@styles/react-datepicker.css'
|
||||
|
||||
const New = () => {
|
||||
return (
|
||||
<NewPost />
|
||||
)
|
||||
}
|
||||
|
||||
export default New
|
|
@ -1,5 +1,6 @@
|
|||
import "styles/globals.css"
|
||||
import "@styles/globals.css"
|
||||
import { ServerThemeProvider } from "next-themes"
|
||||
import { LayoutWrapper } from "./root-layout-wrapper"
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: React.ReactNode
|
||||
|
@ -8,9 +9,9 @@ interface RootLayoutProps {
|
|||
export default function RootLayout({ children }: RootLayoutProps) {
|
||||
return (
|
||||
<ServerThemeProvider
|
||||
forcedTheme="system"
|
||||
disableTransitionOnChange
|
||||
cookieName="drift-theme"
|
||||
attribute="data-theme"
|
||||
>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -48,7 +49,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||
<meta name="theme-color" content="#ffffff" />
|
||||
<title>Drift</title>
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
<body><LayoutWrapper>{children}</LayoutWrapper></body>
|
||||
</html>
|
||||
</ServerThemeProvider>
|
||||
)
|
||||
|
|
21
client/app/page-wrapper.tsx
Normal file
21
client/app/page-wrapper.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
"use client"
|
||||
|
||||
import Header from "@components/header"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import styles from "@styles/Home.module.css"
|
||||
|
||||
export default function PageWrapper({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Page width={"100%"}>
|
||||
<Page.Header>
|
||||
<Header />
|
||||
</Page.Header>
|
||||
|
||||
<Page.Content className={styles.main}>{children}</Page.Content>
|
||||
</Page>
|
||||
)
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1>Hello, world!</h1>
|
||||
</>
|
||||
)
|
||||
import { getWelcomeContent } from "pages/api/welcome"
|
||||
|
||||
const getWelcomeData = async () => {
|
||||
const welcomeContent = await getWelcomeContent()
|
||||
return welcomeContent
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const welcomeData = await getWelcomeData()
|
||||
|
||||
return <h1>{JSON.stringify(welcomeData)}</h1>
|
||||
}
|
||||
|
|
52
client/app/prisma.ts
Normal file
52
client/app/prisma.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Post, PrismaClient, File } from "@prisma/client"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default prisma
|
||||
|
||||
export type {
|
||||
User,
|
||||
AuthTokens,
|
||||
File,
|
||||
Post,
|
||||
PostToAuthors
|
||||
} 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
|
||||
}
|
||||
}
|
66
client/app/root-layout-wrapper.tsx
Normal file
66
client/app/root-layout-wrapper.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
"use client"
|
||||
|
||||
import { CssBaseline, GeistProvider, Themes } from "@geist-ui/core/dist"
|
||||
import { ThemeProvider } from "next-themes"
|
||||
import { SkeletonTheme } from "react-loading-skeleton"
|
||||
import PageWrapper from "./page-wrapper"
|
||||
|
||||
export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||
const skeletonBaseColor = "var(--light-gray)"
|
||||
const skeletonHighlightColor = "var(--lighter-gray)"
|
||||
|
||||
const customTheme = Themes.createFromLight({
|
||||
type: "custom",
|
||||
palette: {
|
||||
background: "var(--bg)",
|
||||
foreground: "var(--fg)",
|
||||
accents_1: "var(--lightest-gray)",
|
||||
accents_2: "var(--lighter-gray)",
|
||||
accents_3: "var(--light-gray)",
|
||||
accents_4: "var(--gray)",
|
||||
accents_5: "var(--darker-gray)",
|
||||
accents_6: "var(--darker-gray)",
|
||||
accents_7: "var(--darkest-gray)",
|
||||
accents_8: "var(--darkest-gray)",
|
||||
border: "var(--light-gray)",
|
||||
warning: "var(--warning)"
|
||||
},
|
||||
expressiveness: {
|
||||
dropdownBoxShadow: "0 0 0 1px var(--light-gray)",
|
||||
shadowSmall: "0 0 0 1px var(--light-gray)",
|
||||
shadowLarge: "0 0 0 1px var(--light-gray)",
|
||||
shadowMedium: "0 0 0 1px var(--light-gray)"
|
||||
},
|
||||
layout: {
|
||||
gap: "var(--gap)",
|
||||
gapHalf: "var(--gap-half)",
|
||||
gapQuarter: "var(--gap-quarter)",
|
||||
gapNegative: "var(--gap-negative)",
|
||||
gapHalfNegative: "var(--gap-half-negative)",
|
||||
gapQuarterNegative: "var(--gap-quarter-negative)",
|
||||
radius: "var(--radius)"
|
||||
},
|
||||
font: {
|
||||
mono: "var(--font-mono)",
|
||||
sans: "var(--font-sans)"
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<GeistProvider themes={[customTheme]} themeType={"custom"}>
|
||||
<SkeletonTheme
|
||||
baseColor={skeletonBaseColor}
|
||||
highlightColor={skeletonHighlightColor}
|
||||
>
|
||||
<ThemeProvider
|
||||
disableTransitionOnChange
|
||||
cookieName="drift-theme"
|
||||
attribute="data-theme"
|
||||
>
|
||||
<CssBaseline />
|
||||
<PageWrapper>{children}</PageWrapper>
|
||||
</ThemeProvider>
|
||||
</SkeletonTheme>
|
||||
</GeistProvider>
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Popover, Button } from "@geist-ui/core"
|
||||
import { Popover, Button } from "@geist-ui/core/dist"
|
||||
import { MoreVertical } from "@geist-ui/icons"
|
||||
|
||||
type Action = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import SettingsGroup from "@components/settings-group"
|
||||
import { Fieldset, useToasts } from "@geist-ui/core"
|
||||
import { Fieldset, useToasts } from "@geist-ui/core/dist"
|
||||
import byteToMB from "@lib/byte-to-mb"
|
||||
import { Post } from "@lib/types"
|
||||
import Table from "rc-table"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Fieldset, useToasts } from "@geist-ui/core"
|
||||
import { Fieldset, useToasts } from "@geist-ui/core/dist"
|
||||
import { User } from "@lib/types"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { adminFetcher } from "."
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Header from "@components/header"
|
||||
import { GeistProvider, CssBaseline, Themes, Page } from "@geist-ui/core"
|
||||
import { GeistProvider, CssBaseline, Themes, Page } from "@geist-ui/core/dist"
|
||||
import type { NextComponentType, NextPageContext } from "next"
|
||||
import { SkeletonTheme } from "react-loading-skeleton"
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"use client"
|
||||
|
||||
import { FormEvent, useEffect, useState } from "react"
|
||||
import styles from "./auth.module.css"
|
||||
import { useRouter } from "next/router"
|
||||
import { useRouter } from "next/navigation"
|
||||
import Link from "../link"
|
||||
import Cookies from "js-cookie"
|
||||
import useSignedIn from "@lib/hooks/use-signed-in"
|
||||
|
@ -12,32 +14,21 @@ const NO_EMPTY_SPACE_REGEX = /^\S*$/
|
|||
const ERROR_MESSAGE =
|
||||
"Provide a non empty username and a password with at least 6 characters"
|
||||
|
||||
const Auth = ({ page }: { page: "signup" | "signin" }) => {
|
||||
const Auth = ({
|
||||
page,
|
||||
requiresServerPassword
|
||||
}: {
|
||||
page: "signup" | "signin"
|
||||
requiresServerPassword?: boolean
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
|
||||
const [username, setUsername] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [serverPassword, setServerPassword] = useState("")
|
||||
const [errorMsg, setErrorMsg] = useState("")
|
||||
const [requiresServerPassword, setRequiresServerPassword] = useState(false)
|
||||
const signingIn = page === "signin"
|
||||
const { signin } = useSignedIn()
|
||||
useEffect(() => {
|
||||
async function fetchRequiresPass() {
|
||||
if (!signingIn) {
|
||||
const resp = await fetch("/server-api/auth/requires-passcode", {
|
||||
method: "GET"
|
||||
})
|
||||
if (resp.ok) {
|
||||
const res = await resp.json()
|
||||
setRequiresServerPassword(res.requiresPasscode)
|
||||
} else {
|
||||
setErrorMsg("Something went wrong. Is the server running?")
|
||||
}
|
||||
}
|
||||
}
|
||||
fetchRequiresPass()
|
||||
}, [page, signingIn])
|
||||
|
||||
const handleJson = (json: any) => {
|
||||
signin(json.token)
|
||||
|
@ -70,9 +61,7 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const signUrl = signingIn
|
||||
? "/server-api/auth/signin"
|
||||
: "/server-api/auth/signup"
|
||||
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)
|
||||
|
@ -141,11 +130,7 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
{errorMsg && (
|
||||
<Note type="error">
|
||||
{errorMsg}
|
||||
</Note>
|
||||
)}
|
||||
{errorMsg && <Note type="error">{errorMsg}</Note>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Badge, Tooltip } from "@geist-ui/core"
|
||||
import { Badge, Tooltip } from "@geist-ui/core/dist"
|
||||
import { timeAgo } from "@lib/time-ago"
|
||||
import { useMemo, useState, useEffect } from "react"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Badge, Tooltip } from "@geist-ui/core"
|
||||
import { Badge, Tooltip } from "@geist-ui/core/dist"
|
||||
import { timeUntil } from "@lib/time-ago"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Badge } from "@geist-ui/core"
|
||||
import { Badge } from "@geist-ui/core/dist"
|
||||
import type { PostVisibility } from "@lib/types"
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import PasswordModal from "@components/new-post/password-modal"
|
||||
import { Button, ButtonGroup, Loading, useToasts } from "@geist-ui/core"
|
||||
import { Button, ButtonGroup, Loading, useToasts } from "@geist-ui/core/dist"
|
||||
import type { PostVisibility } from "@lib/types"
|
||||
import Cookies from "js-cookie"
|
||||
import { useCallback, useState } from "react"
|
||||
|
|
|
@ -7,7 +7,7 @@ import List from "@geist-ui/icons/list"
|
|||
import ImageIcon from "@geist-ui/icons/image"
|
||||
import { RefObject, useMemo } from "react"
|
||||
import styles from "../document.module.css"
|
||||
import { Button, ButtonGroup, Tooltip } from "@geist-ui/core"
|
||||
import { Button, ButtonGroup, Tooltip } from "@geist-ui/core/dist"
|
||||
import { TextareaMarkdownRef } from "textarea-markdown-editor"
|
||||
|
||||
// TODO: clean up
|
||||
|
|
|
@ -11,7 +11,7 @@ import Trash from "@geist-ui/icons/trash"
|
|||
import FormattingIcons from "./formatting-icons"
|
||||
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
|
||||
|
||||
import { Button, Input, Spacer, Tabs, Textarea } from "@geist-ui/core"
|
||||
import { Button, Input, Spacer, Tabs, Textarea } from "@geist-ui/core/dist"
|
||||
import Preview from "@components/preview"
|
||||
|
||||
// import Link from "next/link"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
|
||||
const Error = ({ status }: { status: number }) => {
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ShiftBy from "@components/shift-by"
|
||||
import { Button, Popover } from "@geist-ui/core"
|
||||
import { Button, Popover } from "@geist-ui/core/dist"
|
||||
import ChevronDown from "@geist-ui/icons/chevronDown"
|
||||
import CodeIcon from "@geist-ui/icons/fileFunction"
|
||||
import FileIcon from "@geist-ui/icons/fileText"
|
||||
|
|
|
@ -3,7 +3,7 @@ import MoonIcon from "@geist-ui/icons/moon"
|
|||
import SunIcon from "@geist-ui/icons/sun"
|
||||
// import { useAllThemes, useTheme } from '@geist-ui/core'
|
||||
import styles from "./header.module.css"
|
||||
import { Select } from "@geist-ui/core"
|
||||
import { Select } from "@geist-ui/core/dist"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
const Controls = () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use client';
|
||||
"use client"
|
||||
|
||||
import {
|
||||
ButtonGroup,
|
||||
|
@ -7,7 +7,7 @@ import {
|
|||
Spacer,
|
||||
useBodyScroll,
|
||||
useMediaQuery
|
||||
} from "@geist-ui/core"
|
||||
} from "@geist-ui/core/dist"
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import styles from "./header.module.css"
|
||||
|
@ -27,7 +27,7 @@ import SunIcon from "@geist-ui/icons/sun"
|
|||
import { useTheme } from "next-themes"
|
||||
import useUserData from "@lib/hooks/use-user-data"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/router"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
type Tab = {
|
||||
name: string
|
||||
|
@ -38,7 +38,7 @@ type Tab = {
|
|||
}
|
||||
|
||||
const Header = () => {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [expanded, setExpanded] = useState<boolean>(false)
|
||||
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
|
||||
const isMobile = useMediaQuery("xs", { match: "down" })
|
||||
|
@ -154,7 +154,7 @@ const Header = () => {
|
|||
|
||||
const getButton = useCallback(
|
||||
(tab: Tab) => {
|
||||
const activeStyle = router.pathname === tab.href ? styles.active : ""
|
||||
const activeStyle = pathname === tab.href ? styles.active : ""
|
||||
if (tab.onClick) {
|
||||
return (
|
||||
<Button
|
||||
|
@ -170,21 +170,20 @@ const Header = () => {
|
|||
)
|
||||
} else if (tab.href) {
|
||||
return (
|
||||
(<Link key={tab.value} href={tab.href} className={styles.tab}>
|
||||
<Button
|
||||
className={activeStyle}
|
||||
auto={isMobile ? false : true}
|
||||
icon={tab.icon}
|
||||
shadow={false}
|
||||
>
|
||||
{tab.name ? tab.name : undefined}
|
||||
</Button>
|
||||
|
||||
</Link>)
|
||||
);
|
||||
<Link key={tab.value} href={tab.href} className={styles.tab}>
|
||||
<Button
|
||||
className={activeStyle}
|
||||
auto={isMobile ? false : true}
|
||||
icon={tab.icon}
|
||||
shadow={false}
|
||||
>
|
||||
{tab.name ? tab.name : undefined}
|
||||
</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
[isMobile, onTabChange, router.pathname]
|
||||
[isMobile, onTabChange, pathname]
|
||||
)
|
||||
|
||||
const buttons = useMemo(() => pages.map(getButton), [pages, getButton])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ShiftBy from "@components/shift-by"
|
||||
import { Spacer, Tabs, Card, Textarea, Text } from "@geist-ui/core"
|
||||
import { Spacer, Tabs, Card, Textarea, Text } from "@geist-ui/core/dist"
|
||||
import Image from "next/image"
|
||||
import styles from "./home.module.css"
|
||||
import markdownStyles from "@components/preview/preview.module.css"
|
||||
|
|
|
@ -3,21 +3,14 @@ import NextLink from "next/link"
|
|||
import styles from "./link.module.css"
|
||||
|
||||
type LinkProps = {
|
||||
href: string,
|
||||
colored?: boolean,
|
||||
children: React.ReactNode
|
||||
} & React.ComponentProps<typeof NextLink>
|
||||
|
||||
const Link = ({ href, colored, children, ...props }: LinkProps) => {
|
||||
const { basePath } = useRouter()
|
||||
const propHrefWithoutLeadingSlash =
|
||||
href && href.startsWith("/") ? href.substring(1) : href
|
||||
|
||||
const url = basePath ? `${basePath}/${propHrefWithoutLeadingSlash}` : href
|
||||
|
||||
const Link = ({ colored, children, ...props }: LinkProps) => {
|
||||
const className = colored ? `${styles.link} ${styles.color}` : styles.link
|
||||
return (
|
||||
<NextLink {...props} href={url} className={className}>
|
||||
<NextLink {...props} className={className}>
|
||||
{children}
|
||||
</NextLink>
|
||||
)
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { ChangeEvent, memo } from "react"
|
||||
import { Input } from "@geist-ui/core"
|
||||
import { Input } from "@geist-ui/core/dist"
|
||||
|
||||
import styles from "../post.module.css"
|
||||
|
||||
type props = {
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
description?: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const Description = ({ onChange, description }: props) => {
|
||||
return (
|
||||
<div className={styles.description}>
|
||||
<Input
|
||||
value={description}
|
||||
value={description || ""}
|
||||
onChange={onChange}
|
||||
label="Description"
|
||||
maxLength={256}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Text, useMediaQuery, useTheme, useToasts } from "@geist-ui/core"
|
||||
import { Text, useMediaQuery, useTheme, useToasts } from "@geist-ui/core/dist"
|
||||
import { memo } from "react"
|
||||
import { useDropzone } from "react-dropzone"
|
||||
import styles from "./drag-and-drop.module.css"
|
||||
|
|
|
@ -1,64 +1,52 @@
|
|||
import { Button, useToasts, ButtonDropdown, Input } from "@geist-ui/core"
|
||||
import { useRouter } from "next/router"
|
||||
"use client"
|
||||
|
||||
import { Button, useToasts, Input, ButtonDropdown } from "@geist-ui/core/dist"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import generateUUID from "@lib/generate-uuid"
|
||||
import FileDropzone from "./drag-and-drop"
|
||||
import styles from "./post.module.css"
|
||||
import Title from "./title"
|
||||
import Cookies from "js-cookie"
|
||||
import type {
|
||||
Post as PostType,
|
||||
PostVisibility,
|
||||
Document as DocumentType
|
||||
} from "@lib/types"
|
||||
import type { PostVisibility, Document as DocumentType } from "@lib/types"
|
||||
import PasswordModal from "./password-modal"
|
||||
import EditDocumentList from "@components/edit-document-list"
|
||||
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"
|
||||
|
||||
const emptyDoc = {
|
||||
title: "",
|
||||
content: "",
|
||||
id: generateUUID()
|
||||
}
|
||||
|
||||
const Post = ({
|
||||
initialPost,
|
||||
newPostParent
|
||||
}: {
|
||||
initialPost?: PostType
|
||||
initialPost?: PostWithFiles
|
||||
newPostParent?: string
|
||||
}) => {
|
||||
const { setToast } = useToasts()
|
||||
const router = useRouter()
|
||||
const [title, setTitle] = useState<string>()
|
||||
const [description, setDescription] = useState<string>()
|
||||
const [expiresAt, setExpiresAt] = useState<Date | null>(null)
|
||||
|
||||
const emptyDoc = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: "",
|
||||
content: "",
|
||||
id: generateUUID()
|
||||
}
|
||||
],
|
||||
[]
|
||||
const [title, setTitle] = useState(
|
||||
getTitleForPostCopy(initialPost?.title) || ""
|
||||
)
|
||||
const [description, setDescription] = useState(initialPost?.description || "")
|
||||
const [expiresAt, setExpiresAt] = useState(initialPost?.expiresAt)
|
||||
|
||||
const [docs, setDocs] = useState<DocumentType[]>(emptyDoc)
|
||||
const defaultDocs: DocumentType[] = initialPost
|
||||
? initialPost.files?.map((doc) => ({
|
||||
title: doc.title,
|
||||
content: doc.content,
|
||||
id: doc.id
|
||||
}))
|
||||
: [emptyDoc]
|
||||
|
||||
// the /new/from/{id} route fetches an initial post
|
||||
useEffect(() => {
|
||||
if (initialPost) {
|
||||
setDocs(
|
||||
initialPost.files?.map((doc) => ({
|
||||
title: doc.title,
|
||||
content: doc.content,
|
||||
id: doc.id
|
||||
})) || emptyDoc
|
||||
)
|
||||
|
||||
setTitle(getTitleForPostCopy(initialPost.title))
|
||||
setDescription(initialPost.description)
|
||||
}
|
||||
}, [emptyDoc, initialPost])
|
||||
const [docs, setDocs] = useState(defaultDocs)
|
||||
|
||||
const [passwordModalVisible, setPasswordModalVisible] = useState(false)
|
||||
|
||||
|
@ -151,13 +139,13 @@ const Post = ({
|
|||
return
|
||||
}
|
||||
|
||||
await sendRequest("/server-api/posts/create", {
|
||||
await sendRequest("/api/posts/create", {
|
||||
title,
|
||||
files: docs,
|
||||
visibility,
|
||||
password,
|
||||
userId: Cookies.get("drift-userid") || "",
|
||||
expiresAt,
|
||||
expiresAt: expiresAt || null,
|
||||
parentId: newPostParent
|
||||
})
|
||||
},
|
||||
|
@ -233,18 +221,15 @@ const Post = ({
|
|||
// }))
|
||||
// }
|
||||
|
||||
const onPaste = useCallback(
|
||||
(e: any) => {
|
||||
const pastedText = e.clipboardData.getData("text")
|
||||
const onPaste = (e: ClipboardEvent) => {
|
||||
const pastedText = e.clipboardData?.getData("text")
|
||||
|
||||
if (pastedText) {
|
||||
if (!title) {
|
||||
setTitle("Pasted text")
|
||||
}
|
||||
if (pastedText) {
|
||||
if (!title) {
|
||||
setTitle("Pasted text")
|
||||
}
|
||||
},
|
||||
[title]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const CustomTimeInput = ({
|
||||
date,
|
||||
|
@ -328,10 +313,10 @@ const Post = ({
|
|||
/>
|
||||
}
|
||||
<ButtonDropdown loading={isSubmitting} type="success">
|
||||
<ButtonDropdown.Item onClick={() => onSubmit("unlisted")}>
|
||||
<ButtonDropdown.Item main onClick={() => onSubmit("unlisted")}>
|
||||
Create Unlisted
|
||||
</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item main onClick={() => onSubmit("private")}>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit("private")}>
|
||||
Create Private
|
||||
</ButtonDropdown.Item>
|
||||
<ButtonDropdown.Item onClick={() => onSubmit("public")}>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Modal, Note, Spacer, Input } from "@geist-ui/core"
|
||||
import { Modal, Note, Spacer, Input } from "@geist-ui/core/dist"
|
||||
import { useState } from "react"
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ChangeEvent, memo, useEffect, useState } from "react"
|
||||
import { Text } from "@geist-ui/core"
|
||||
import { Text } from "@geist-ui/core/dist"
|
||||
|
||||
import ShiftBy from "@components/shift-by"
|
||||
import styles from "../post.module.css"
|
||||
import { Input } from "@geist-ui/core"
|
||||
import { Input } from "@geist-ui/core/dist"
|
||||
|
||||
const titlePlaceholders = [
|
||||
"How to...",
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
}
|
||||
|
||||
.error {
|
||||
background: red;
|
||||
background: #f33;
|
||||
}
|
||||
|
||||
.type {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import Head from "next/head"
|
||||
import React from "react"
|
||||
|
||||
type PageSeoProps = {
|
||||
|
@ -15,10 +14,8 @@ const PageSeo = ({
|
|||
}: PageSeoProps) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
{!isPrivate && <meta name="description" content={description} />}
|
||||
</Head>
|
||||
<title>Drift - {title}</title>
|
||||
{!isPrivate && <meta name="description" content={description} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Input, Text } from "@geist-ui/core"
|
||||
import { Button, Input, Text } from "@geist-ui/core/dist"
|
||||
|
||||
import styles from "./post-list.module.css"
|
||||
import ListItemSkeleton from "./list-item-skeleton"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Skeleton from "react-loading-skeleton"
|
||||
import { Card, Divider, Grid, Spacer } from "@geist-ui/core"
|
||||
import { Card, Divider, Grid, Spacer } from "@geist-ui/core/dist"
|
||||
|
||||
const ListItemSkeleton = () => (
|
||||
<Card>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import NextLink from "next/link"
|
||||
import VisibilityBadge from "../badges/visibility-badge"
|
||||
import { Text, Card, Tooltip, Divider, Badge, Button } from "@geist-ui/core"
|
||||
import { Text, Card, Tooltip, Divider, Badge, Button } from "@geist-ui/core/dist"
|
||||
import { File, Post } from "@lib/types"
|
||||
import FadeIn from "@components/fade-in"
|
||||
import Trash from "@geist-ui/icons/trash"
|
||||
|
|
|
@ -5,7 +5,7 @@ import styles from "./post-page.module.css"
|
|||
import homeStyles from "@styles/Home.module.css"
|
||||
|
||||
import type { File, Post, PostVisibility } from "@lib/types"
|
||||
import { Page, Button, Text, ButtonGroup, useMediaQuery } from "@geist-ui/core"
|
||||
import { Page, Button, Text, ButtonGroup, useMediaQuery } from "@geist-ui/core/dist"
|
||||
import { useEffect, useState } from "react"
|
||||
import Archive from "@geist-ui/icons/archive"
|
||||
import Edit from "@geist-ui/icons/edit"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import PasswordModal from "@components/new-post/password-modal"
|
||||
import { Page, useToasts } from "@geist-ui/core"
|
||||
import { Page, useToasts } from "@geist-ui/core/dist"
|
||||
import { Post } from "@lib/types"
|
||||
import { useRouter } from "next/router"
|
||||
import { useState } from "react"
|
||||
|
|
|
@ -16,7 +16,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => {
|
|||
useEffect(() => {
|
||||
async function fetchPost() {
|
||||
if (fileId) {
|
||||
const resp = await fetch(`/api/html/${fileId}`, {
|
||||
const resp = await fetch(`/api/file/html/${fileId}`, {
|
||||
method: "GET"
|
||||
})
|
||||
if (resp.ok) {
|
||||
|
@ -25,16 +25,19 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => {
|
|||
setIsLoading(false)
|
||||
}
|
||||
} else if (content) {
|
||||
const resp = await fetch("/server-api/files/html", {
|
||||
method: "POST",
|
||||
// add title and query to url params
|
||||
const urlQuery = new URLSearchParams({
|
||||
title: title || "",
|
||||
content
|
||||
})
|
||||
|
||||
const resp = await fetch(`/api/files/get-html?${urlQuery}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${Cookies.get("drift-token") || ""}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
content
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
if (resp.ok) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Tooltip, Button, Spacer } from "@geist-ui/core"
|
||||
import { Tooltip, Button, Spacer } from "@geist-ui/core/dist"
|
||||
import ChevronUp from "@geist-ui/icons/chevronUpCircleFill"
|
||||
import { useEffect, useState } from "react"
|
||||
import styles from "./scroll.module.css"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Fieldset, Text, Divider } from "@geist-ui/core"
|
||||
import { Fieldset, Text, Divider } from "@geist-ui/core/dist"
|
||||
import styles from "./settings-group.module.css"
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Input, Button, useToasts } from "@geist-ui/core"
|
||||
import { Input, Button, useToasts } from "@geist-ui/core/dist"
|
||||
import Cookies from "js-cookie"
|
||||
import { useState } from "react"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Note, Input, Textarea, Button, useToasts } from "@geist-ui/core"
|
||||
import { Note, Input, Textarea, Button, useToasts } from "@geist-ui/core/dist"
|
||||
import useUserData from "@lib/hooks/use-user-data"
|
||||
import Cookies from "js-cookie"
|
||||
import { useEffect, useState } from "react"
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
Textarea,
|
||||
Tooltip,
|
||||
Tag
|
||||
} from "@geist-ui/core"
|
||||
} from "@geist-ui/core/dist"
|
||||
import HtmlPreview from "@components/preview"
|
||||
import FadeIn from "@components/fade-in"
|
||||
|
||||
|
|
27
client/lib/api/generate-access-token.ts
Normal file
27
client/lib/api/generate-access-token.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import config from "@lib/config"
|
||||
import { User } from "@prisma/client"
|
||||
import prisma from "app/prisma"
|
||||
import { sign } from "jsonwebtoken"
|
||||
|
||||
export async function generateAccessToken(user: User) {
|
||||
const token = sign({ id: user.id }, config.jwt_secret, { expiresIn: "2d" })
|
||||
|
||||
await prisma.authTokens.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
token: token
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: set expiredReason?
|
||||
prisma.authTokens.deleteMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
token: {
|
||||
not: token
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return token
|
||||
}
|
39
client/lib/api/get-html-from-drift-file.ts
Normal file
39
client/lib/api/get-html-from-drift-file.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import markdown from "../render-markdown"
|
||||
import type { File } from "app/prisma"
|
||||
/**
|
||||
* returns rendered HTML from a Drift file
|
||||
*/
|
||||
function getHtmlFromFile({ content, title }: Pick<File, "content" | "title">) {
|
||||
const renderAsMarkdown = [
|
||||
"markdown",
|
||||
"md",
|
||||
"mdown",
|
||||
"mkdn",
|
||||
"mkd",
|
||||
"mdwn",
|
||||
"mdtxt",
|
||||
"mdtext",
|
||||
"text",
|
||||
""
|
||||
]
|
||||
const fileType = () => {
|
||||
const pathParts = title.split(".")
|
||||
const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : ""
|
||||
return language
|
||||
}
|
||||
const type = fileType()
|
||||
let contentToRender: string = content || ""
|
||||
|
||||
if (!renderAsMarkdown.includes(type)) {
|
||||
contentToRender = `~~~${type}
|
||||
${content}
|
||||
~~~`
|
||||
} else {
|
||||
contentToRender = "\n" + content
|
||||
}
|
||||
|
||||
const html = markdown(contentToRender)
|
||||
return html
|
||||
}
|
||||
|
||||
export default getHtmlFromFile
|
64
client/lib/api/jwt.ts
Normal file
64
client/lib/api/jwt.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
// next api route jwt middleware; check if the user has a valid jwt token
|
||||
|
||||
import config from "@lib/config"
|
||||
import { User } from "@prisma/client"
|
||||
import prisma from "app/prisma"
|
||||
import * as jwt from "jsonwebtoken"
|
||||
import next, { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
type ReqWithUser = NextApiRequest & {
|
||||
user?: User
|
||||
}
|
||||
|
||||
type WrappedHandler = (req: ReqWithUser, res: NextApiResponse) => Promise<void>
|
||||
|
||||
// usage: useJwt(otherHandler)
|
||||
|
||||
// we want the usage to be the user writing their API route and exporting it with useJwt(handler)
|
||||
|
||||
// uses prisma
|
||||
export async function withJwt(
|
||||
origHandler: NextApiHandler
|
||||
): Promise<WrappedHandler | void> {
|
||||
return async (req: ReqWithUser, res: NextApiResponse) => {
|
||||
const authHeader = req ? req.headers["authorization"] : undefined
|
||||
const token = authHeader && authHeader.split(" ")[1]
|
||||
|
||||
if (token == null) return res.status(401).send("Unauthorized")
|
||||
|
||||
const authToken = await prisma.authTokens.findUnique({
|
||||
where: { id: token }
|
||||
})
|
||||
if (authToken == null) {
|
||||
return res.status(401).send("Unauthorized")
|
||||
}
|
||||
|
||||
if (authToken.deletedAt) {
|
||||
return res.status(401).json({
|
||||
message: "Token is no longer valid"
|
||||
})
|
||||
}
|
||||
|
||||
jwt.verify(token, config.jwt_secret, async (err: any, user: any) => {
|
||||
if (err) return res.status(403).send("Forbidden")
|
||||
const userObj = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
displayName: true,
|
||||
bio: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
deletedAt: true
|
||||
}
|
||||
})
|
||||
if (!userObj) {
|
||||
return res.status(403).send("Forbidden")
|
||||
}
|
||||
|
||||
;(req as ReqWithUser).user = user
|
||||
return origHandler(req, res)
|
||||
})
|
||||
}
|
||||
}
|
13
client/lib/api/parse-url-query.ts
Normal file
13
client/lib/api/parse-url-query.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Parses a URL query string from string | string[] | ...
|
||||
* to string | undefined. If it's an array, we return the last item.
|
||||
*/
|
||||
export function parseUrlQuery(query: string | string[] | undefined) {
|
||||
if (typeof query === "string") {
|
||||
return query
|
||||
} else if (Array.isArray(query)) {
|
||||
return query[query.length - 1]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
89
client/lib/config.ts
Normal file
89
client/lib/config.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
type Config = {
|
||||
// port: number
|
||||
jwt_secret: string
|
||||
drift_home: string
|
||||
is_production: boolean
|
||||
memory_db: boolean
|
||||
enable_admin: boolean
|
||||
secret_key: string
|
||||
registration_password: string
|
||||
welcome_content: string
|
||||
welcome_title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
type EnvironmentValue = string | undefined
|
||||
type Environment = { [key: string]: EnvironmentValue }
|
||||
|
||||
export const config = (env: Environment): Config => {
|
||||
const stringToBoolean = (str: EnvironmentValue): boolean => {
|
||||
if (str === "true") {
|
||||
return true
|
||||
} else if (str === "false") {
|
||||
return false
|
||||
} else if (str) {
|
||||
throw new Error(`Invalid boolean value: ${str}`)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const throwIfUndefined = (str: EnvironmentValue, name: string): string => {
|
||||
if (str === undefined) {
|
||||
throw new Error(`Missing environment variable: ${name}`)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
const defaultIfUndefined = (
|
||||
str: EnvironmentValue,
|
||||
defaultValue: string
|
||||
): string => {
|
||||
if (str === undefined) {
|
||||
return defaultValue
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
const validNodeEnvs = (str: EnvironmentValue) => {
|
||||
const valid = ["development", "production", "test"]
|
||||
if (str && !valid.includes(str)) {
|
||||
throw new Error(`Invalid NODE_ENV set: ${str}`)
|
||||
} else if (!str) {
|
||||
console.warn("No NODE_ENV specified, defaulting to development")
|
||||
} else {
|
||||
console.log(`Using NODE_ENV: ${str}`)
|
||||
}
|
||||
}
|
||||
|
||||
const is_production = env.NODE_ENV === "production"
|
||||
|
||||
const developmentDefault = (
|
||||
str: EnvironmentValue,
|
||||
name: string,
|
||||
defaultValue: string
|
||||
): string => {
|
||||
if (is_production) return throwIfUndefined(str, name)
|
||||
return defaultIfUndefined(str, defaultValue)
|
||||
}
|
||||
|
||||
validNodeEnvs(env.NODE_ENV)
|
||||
|
||||
const config: Config = {
|
||||
// port: env.PORT ? parseInt(env.PORT) : 3000,
|
||||
jwt_secret: env.JWT_SECRET || "myjwtsecret",
|
||||
drift_home: env.DRIFT_HOME || "~/.drift",
|
||||
is_production,
|
||||
memory_db: stringToBoolean(env.MEMORY_DB),
|
||||
enable_admin: stringToBoolean(env.ENABLE_ADMIN),
|
||||
secret_key: developmentDefault(env.SECRET_KEY, "SECRET_KEY", "secret"),
|
||||
registration_password: env.REGISTRATION_PASSWORD ?? "",
|
||||
welcome_content: env.WELCOME_CONTENT ?? "",
|
||||
welcome_title: env.WELCOME_TITLE ?? "",
|
||||
url: 'http://localhost:3000'
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
export default config(process.env)
|
|
@ -14,7 +14,9 @@ const replaceLastInString = (
|
|||
)
|
||||
}
|
||||
|
||||
const getTitleForPostCopy = (title: string) => {
|
||||
const getTitleForPostCopy = (title?: string) => {
|
||||
if (!title) return ""
|
||||
|
||||
const numberAtEndOfTitle = title.split(" ").pop()
|
||||
if (numberAtEndOfTitle) {
|
||||
const number = parseInt(numberAtEndOfTitle)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { User } from "@lib/types"
|
||||
import Cookies from "js-cookie"
|
||||
import { useRouter } from "next/router"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
const useUserData = () => {
|
||||
const [authToken, setAuthToken] = useState<string>(
|
||||
|
|
189
client/lib/render-markdown.tsx
Normal file
189
client/lib/render-markdown.tsx
Normal file
|
@ -0,0 +1,189 @@
|
|||
import { marked } from "marked"
|
||||
// import Highlight, { defaultProps, Language } from "prism-react-renderer"
|
||||
import { renderToStaticMarkup } from "react-dom/server"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
// // image sizes. DDoS Safe?
|
||||
// const imageSizeLink = /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?(?:\s+=(?:[\w%]+)?x(?:[\w%]+)?)?)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/;
|
||||
// //@ts-ignore
|
||||
// Lexer.rules.inline.normal.link = imageSizeLink;
|
||||
// //@ts-ignore
|
||||
// Lexer.rules.inline.gfm.link = imageSizeLink;
|
||||
// //@ts-ignore
|
||||
// Lexer.rules.inline.breaks.link = imageSizeLink;
|
||||
|
||||
//@ts-ignore
|
||||
// delete defaultProps.theme
|
||||
// import linkStyles from '../components/link/link.module.css'
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
// @ts-ignore
|
||||
renderer.heading = (text, level, _, slugger) => {
|
||||
const id = slugger.slug(text)
|
||||
const Component = `h${level}`
|
||||
|
||||
return (
|
||||
<h1>
|
||||
<Link
|
||||
href={`#${id}`}
|
||||
id={id}
|
||||
style={{ color: "inherit" }}
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
></Link>
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
|
||||
// renderer.link = (href, _, text) => {
|
||||
// const isHrefLocal = href?.startsWith('/') || href?.startsWith('#')
|
||||
// if (isHrefLocal) {
|
||||
// return renderToStaticMarkup(
|
||||
// <a href={href || ''}>
|
||||
// {text}
|
||||
// </a>
|
||||
// )
|
||||
// }
|
||||
|
||||
// // dirty hack
|
||||
// // if text contains elements, render as html
|
||||
// return <a href={href || ""} target="_blank" rel="noopener noreferrer" dangerouslySetInnerHTML={{ __html: convertHtmlEntities(text) }} ></a>
|
||||
// }
|
||||
|
||||
// @ts-ignore
|
||||
renderer.image = function (href, _, text) {
|
||||
return <Image loading="lazy" src="${href}" alt="${text}" layout="fill" />
|
||||
}
|
||||
|
||||
renderer.checkbox = () => ""
|
||||
// @ts-ignore
|
||||
renderer.listitem = (text, task, checked) => {
|
||||
if (task) {
|
||||
return (
|
||||
<li className="reset">
|
||||
<span className="check">
|
||||
​
|
||||
<input type="checkbox" disabled checked={checked} />
|
||||
</span>
|
||||
<span>${text}</span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return `<li>${text}</li>`
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
renderer.code = (code: string, language: string) => {
|
||||
return (
|
||||
<pre>
|
||||
{/* {title && <code>{title} </code>} */}
|
||||
{/* {language && title && <code style={{}}> {language} </code>} */}
|
||||
<Code
|
||||
language={language}
|
||||
// title={title}
|
||||
code={code}
|
||||
// highlight={highlight}
|
||||
/>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
headerIds: true,
|
||||
renderer
|
||||
})
|
||||
|
||||
const markdown = (markdown: string) => marked(markdown)
|
||||
|
||||
export default markdown
|
||||
|
||||
const Code = ({
|
||||
code,
|
||||
language,
|
||||
highlight,
|
||||
title,
|
||||
...props
|
||||
}: {
|
||||
code: string
|
||||
language: string
|
||||
highlight?: string
|
||||
title?: string
|
||||
}) => {
|
||||
if (!language)
|
||||
return (
|
||||
<>
|
||||
<code {...props} dangerouslySetInnerHTML={{ __html: code }} />
|
||||
</>
|
||||
)
|
||||
|
||||
const highlightedLines = highlight
|
||||
? //@ts-ignore
|
||||
highlight.split(",").reduce((lines, h) => {
|
||||
if (h.includes("-")) {
|
||||
// Expand ranges like 3-5 into [3,4,5]
|
||||
const [start, end] = h.split("-").map(Number)
|
||||
const x = Array(end - start + 1)
|
||||
.fill(undefined)
|
||||
.map((_, i) => i + start)
|
||||
return [...lines, ...x]
|
||||
}
|
||||
|
||||
return [...lines, Number(h)]
|
||||
}, [])
|
||||
: ""
|
||||
|
||||
// https://mdxjs.com/guides/syntax-harkedighlighting#all-together
|
||||
return (
|
||||
<>
|
||||
{/* <Highlight
|
||||
{...defaultProps}
|
||||
code={code.trim()}
|
||||
language={language as Language}
|
||||
>
|
||||
{({
|
||||
className,
|
||||
style,
|
||||
tokens,
|
||||
getLineProps,
|
||||
getTokenProps
|
||||
}: {
|
||||
className: string
|
||||
style: any
|
||||
tokens: any
|
||||
getLineProps: any
|
||||
getTokenProps: any
|
||||
}) => (
|
||||
<code className={className} style={{ ...style }}>
|
||||
{tokens.map((line: string[], i: number) => (
|
||||
<div
|
||||
key={i}
|
||||
{...getLineProps({ line, key: i })}
|
||||
style={
|
||||
//@ts-ignore
|
||||
highlightedLines.includes((i + 1).toString())
|
||||
? {
|
||||
background: "var(--highlight)",
|
||||
margin: "0 -1rem",
|
||||
padding: "0 1rem"
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{line.map((token: string, key: number) => (
|
||||
<span key={key} {...getTokenProps({ token, key })} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
)}
|
||||
</Highlight> */}
|
||||
<>
|
||||
<code {...props} dangerouslySetInnerHTML={{ __html: code }} />
|
||||
</>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -65,7 +65,7 @@ export function middleware(req: NextRequest, event: NextFetchEvent) {
|
|||
export const config = {
|
||||
match: [
|
||||
"/signout",
|
||||
"/",
|
||||
// "/",
|
||||
"/signin",
|
||||
"/signup",
|
||||
"/new",
|
||||
|
|
|
@ -3,25 +3,31 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
"dev": "next dev --port 3000",
|
||||
"build": "next build",
|
||||
"start": "next start --port 3001",
|
||||
"start": "next start --port 3000",
|
||||
"lint": "next lint && prettier --list-different --config .prettierrc '{components,lib,pages}/**/*.{ts,tsx}' --write",
|
||||
"analyze": "cross-env ANALYZE=true next build",
|
||||
"find:unused": "next-unused"
|
||||
"find:unused": "next-unused",
|
||||
"prisma": "prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@geist-ui/core": "^2.3.8",
|
||||
"@geist-ui/icons": "1.0.2",
|
||||
"@prisma/client": "^4.6.0",
|
||||
"@types/cookie": "0.5.1",
|
||||
"@types/js-cookie": "3.0.2",
|
||||
"bcrypt": "^5.1.0",
|
||||
"client-zip": "2.2.1",
|
||||
"clsx": "^1.2.1",
|
||||
"cookie": "0.5.0",
|
||||
"dotenv": "16.0.0",
|
||||
"js-cookie": "3.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"marked": "^4.2.2",
|
||||
"next": "13.0.3-canary.2",
|
||||
"next-themes": "npm:@wits/next-themes@0.2.7",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"rc-table": "7.24.1",
|
||||
"react": "18.2.0",
|
||||
"react-datepicker": "4.8.0",
|
||||
|
@ -30,10 +36,14 @@
|
|||
"react-hot-toast": "^2.4.0",
|
||||
"react-loading-skeleton": "3.1.0",
|
||||
"swr": "1.3.0",
|
||||
"textarea-markdown-editor": "0.1.13"
|
||||
"textarea-markdown-editor": "0.1.13",
|
||||
"zod": "^3.19.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.1.6",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/marked": "^4.0.7",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/react": "18.0.9",
|
||||
"@types/react-datepicker": "4.4.1",
|
||||
|
@ -43,6 +53,7 @@
|
|||
"eslint-config-next": "13.0.2",
|
||||
"next-unused": "0.0.6",
|
||||
"prettier": "2.6.2",
|
||||
"prisma": "^4.6.0",
|
||||
"typescript": "4.6.4",
|
||||
"typescript-plugin-css-modules": "3.4.0"
|
||||
},
|
||||
|
@ -59,8 +70,5 @@
|
|||
"components",
|
||||
"lib"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"next": "13.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CssBaseline } from "@geist-ui/core"
|
||||
import { CssBaseline } from "@geist-ui/core/dist"
|
||||
import Document, {
|
||||
Html,
|
||||
Head,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import styles from "@styles/Home.module.css"
|
||||
|
||||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import { useEffect } from "react"
|
||||
import Admin from "@components/admin"
|
||||
import useSignedIn from "@lib/hooks/use-signed-in"
|
||||
|
|
32
client/pages/api/auth/backup.ts
Normal file
32
client/pages/api/auth/backup.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import config from "@lib/config"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
const handleRequiresPasscode = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) => {
|
||||
const requiresPasscode = Boolean(config.registration_password)
|
||||
return res.json({ requiresPasscode })
|
||||
}
|
||||
|
||||
const PATH_TO_HANDLER = {
|
||||
"requires-passcode": handleRequiresPasscode
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { slug } = req.query
|
||||
|
||||
if (!slug || Array.isArray(slug)) {
|
||||
return res.status(400).json({ error: "Missing param" })
|
||||
}
|
||||
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
if (PATH_TO_HANDLER[slug as keyof typeof PATH_TO_HANDLER]) {
|
||||
return PATH_TO_HANDLER[slug as keyof typeof PATH_TO_HANDLER](req, res)
|
||||
}
|
||||
default:
|
||||
return res.status(405).json({ error: "Method not allowed" })
|
||||
}
|
||||
}
|
35
client/pages/api/auth/requires-passcode.ts
Normal file
35
client/pages/api/auth/requires-passcode.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import config from "@lib/config"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
|
||||
export const getRequiresPasscode = async () => {
|
||||
const requiresPasscode = Boolean(config.registration_password)
|
||||
return requiresPasscode
|
||||
}
|
||||
|
||||
const handleRequiresPasscode = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) => {
|
||||
return res.json({ requiresPasscode: await getRequiresPasscode() })
|
||||
}
|
||||
|
||||
export default async function requiresPasscode(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { slug } = req.query
|
||||
|
||||
if (!slug || Array.isArray(slug)) {
|
||||
return res.status(400).json({ error: "Missing param" })
|
||||
}
|
||||
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
if (slug === "requires-passcode") {
|
||||
return handleRequiresPasscode(req, res)
|
||||
}
|
||||
default:
|
||||
return res.status(405).json({ error: "Method not allowed" })
|
||||
}
|
||||
}
|
36
client/pages/api/auth/signin.ts
Normal file
36
client/pages/api/auth/signin.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import config from "@lib/config"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import prisma from "app/prisma"
|
||||
import bcrypt from "bcrypt"
|
||||
import { generateAccessToken } from "@lib/api/generate-access-token"
|
||||
import Cookies from "js-cookie"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { username, password } = req.body
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: "Missing param" })
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: "Unauthorized" })
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password)
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({ error: "Unauthorized" })
|
||||
}
|
||||
|
||||
const token = await generateAccessToken(user)
|
||||
Cookies.set("drift-user", user.id, { path: "/" })
|
||||
Cookies.set("drift-token", token, { path: "/" })
|
||||
return res.status(201).json({ token: token, userId: user.id })
|
||||
}
|
45
client/pages/api/auth/signup.ts
Normal file
45
client/pages/api/auth/signup.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import config from "@lib/config"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
import prisma from "app/prisma"
|
||||
import bcrypt, { genSalt } from "bcrypt"
|
||||
import { generateAccessToken } from "@lib/api/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 generateAccessToken(user)
|
||||
|
||||
return res.status(201).json({ token: token, userId: user.id })
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
52
client/pages/api/file/get-html.ts
Normal file
52
client/pages/api/file/get-html.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import getHtmlFromFile from "@lib/api/get-html-from-drift-file"
|
||||
import { parseUrlQuery } from "@lib/api/parse-url-query"
|
||||
import prisma from "app/prisma"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
const query = req.query
|
||||
const fileId = parseUrlQuery(query.fileId)
|
||||
const content = parseUrlQuery(query.content)
|
||||
const title = parseUrlQuery(query.title)
|
||||
|
||||
if (fileId && (content || title)) {
|
||||
return res.status(400).json({ error: "Too many arguments" })
|
||||
}
|
||||
|
||||
if (fileId) {
|
||||
// TODO: abstract to getFileById
|
||||
const file = await prisma.file.findUnique({
|
||||
where: {
|
||||
id: fileId
|
||||
}
|
||||
})
|
||||
|
||||
if (!file) {
|
||||
return res.status(404).json({ error: "File not found" })
|
||||
}
|
||||
|
||||
return res.json(file.html)
|
||||
} else {
|
||||
if (!content || !title) {
|
||||
return res.status(400).json({ error: "Missing arguments" })
|
||||
}
|
||||
|
||||
const renderedHTML = getHtmlFromFile({
|
||||
title,
|
||||
content
|
||||
})
|
||||
|
||||
res.setHeader("Content-Type", "text/plain")
|
||||
res.status(200).write(renderedHTML)
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
default:
|
||||
return res.status(405).json({ error: "Method not allowed" })
|
||||
}
|
||||
}
|
32
client/pages/api/user/[slug].ts
Normal file
32
client/pages/api/user/[slug].ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { withJwt } from "@lib/api/jwt"
|
||||
import config from "@lib/config"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
const handleSelf = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) => {
|
||||
|
||||
}
|
||||
|
||||
const PATH_TO_HANDLER = {
|
||||
"self": handleRequiresPasscode
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default withJwt((req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { slug } = req.query
|
||||
|
||||
if (!slug || Array.isArray(slug)) {
|
||||
return res.status(400).json({ error: "Missing param" })
|
||||
}
|
||||
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
if (PATH_TO_HANDLER[slug as keyof typeof PATH_TO_HANDLER]) {
|
||||
return PATH_TO_HANDLER[slug as keyof typeof PATH_TO_HANDLER](req, res)
|
||||
}
|
||||
default:
|
||||
return res.status(405).json({ error: "Method not allowed" })
|
||||
}
|
||||
})
|
34
client/pages/api/welcome.ts
Normal file
34
client/pages/api/welcome.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
// a nextjs api handerl
|
||||
|
||||
import config from "@lib/config"
|
||||
import markdown from "@lib/render-markdown"
|
||||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export const getWelcomeContent = async () => {
|
||||
const introContent = config.welcome_content
|
||||
const introTitle = config.welcome_title
|
||||
// if (!introContent || !introTitle) {
|
||||
// return {}
|
||||
// }
|
||||
|
||||
console.log(introContent)
|
||||
|
||||
|
||||
return {
|
||||
title: introTitle,
|
||||
content: introContent,
|
||||
rendered: markdown(introContent)
|
||||
}
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const welcomeContent = await getWelcomeContent()
|
||||
if (!welcomeContent) {
|
||||
return res.status(500).json({ error: "Missing welcome content" })
|
||||
}
|
||||
|
||||
return res.json(welcomeContent)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Note, Page, Text } from "@geist-ui/core"
|
||||
import { Note, Page, Text } from "@geist-ui/core/dist"
|
||||
import styles from "@styles/Home.module.css"
|
||||
|
||||
const Expired = () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import styles from "@styles/Home.module.css"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import HomeComponent from "@components/home"
|
||||
import { Page, Text } from "@geist-ui/core"
|
||||
import { Page, Text } from "@geist-ui/core/dist"
|
||||
import type { GetStaticProps } from "next"
|
||||
import { InferGetStaticPropsType } from "next"
|
||||
type Props =
|
||||
|
|
|
@ -5,7 +5,7 @@ import MyPosts from "@components/my-posts"
|
|||
import cookie from "cookie"
|
||||
import type { GetServerSideProps } from "next"
|
||||
import { Post } from "@lib/types"
|
||||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
|
||||
const Home = ({
|
||||
morePosts,
|
||||
|
|
|
@ -2,7 +2,7 @@ import styles from "@styles/Home.module.css"
|
|||
import NewPost from "@components/new-post"
|
||||
import Header from "@components/header"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import Head from "next/head"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { Post } from "@lib/types"
|
|
@ -2,7 +2,7 @@ import styles from "@styles/Home.module.css"
|
|||
import NewPost from "@components/new-post"
|
||||
import Header from "@components/header"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import Head from "next/head"
|
||||
|
||||
const New = () => {
|
|
@ -7,7 +7,7 @@ import {
|
|||
Page,
|
||||
Note,
|
||||
Textarea
|
||||
} from "@geist-ui/core"
|
||||
} from "@geist-ui/core/dist"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import styles from "@styles/Home.module.css"
|
||||
import SettingsPage from "@components/settings"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import Auth from "@components/auth"
|
||||
import styles from "@styles/Home.module.css"
|
|
@ -1,4 +1,4 @@
|
|||
import { Page } from "@geist-ui/core"
|
||||
import { Page } from "@geist-ui/core/dist"
|
||||
import Auth from "@components/auth"
|
||||
import PageSeo from "@components/page-seo"
|
||||
import styles from "@styles/Home.module.css"
|
|
@ -4,12 +4,17 @@ specifiers:
|
|||
'@geist-ui/core': ^2.3.8
|
||||
'@geist-ui/icons': 1.0.2
|
||||
'@next/bundle-analyzer': 12.1.6
|
||||
'@prisma/client': ^4.6.0
|
||||
'@types/bcrypt': ^5.0.0
|
||||
'@types/cookie': 0.5.1
|
||||
'@types/js-cookie': 3.0.2
|
||||
'@types/jsonwebtoken': ^8.5.9
|
||||
'@types/marked': ^4.0.7
|
||||
'@types/node': 17.0.23
|
||||
'@types/react': 18.0.9
|
||||
'@types/react-datepicker': 4.4.1
|
||||
'@types/react-dom': 18.0.3
|
||||
bcrypt: ^5.1.0
|
||||
client-zip: 2.2.1
|
||||
clsx: ^1.2.1
|
||||
cookie: 0.5.0
|
||||
|
@ -18,10 +23,14 @@ specifiers:
|
|||
eslint: 8.27.0
|
||||
eslint-config-next: 13.0.2
|
||||
js-cookie: 3.0.1
|
||||
jsonwebtoken: ^8.5.1
|
||||
marked: ^4.2.2
|
||||
next: 13.0.3-canary.2
|
||||
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
|
||||
rc-table: 7.24.1
|
||||
react: 18.2.0
|
||||
react-datepicker: 4.8.0
|
||||
|
@ -34,19 +43,25 @@ specifiers:
|
|||
textarea-markdown-editor: 0.1.13
|
||||
typescript: 4.6.4
|
||||
typescript-plugin-css-modules: 3.4.0
|
||||
zod: ^3.19.1
|
||||
|
||||
dependencies:
|
||||
'@geist-ui/core': 2.3.8_biqbaboplfbrettd7655fr4n2y
|
||||
'@geist-ui/icons': 1.0.2_zhza2kbnl2wkkf7vqdl3ton2f4
|
||||
'@prisma/client': 4.6.0_prisma@4.6.0
|
||||
'@types/cookie': 0.5.1
|
||||
'@types/js-cookie': 3.0.2
|
||||
bcrypt: 5.1.0
|
||||
client-zip: 2.2.1
|
||||
clsx: 1.2.1
|
||||
cookie: 0.5.0
|
||||
dotenv: 16.0.0
|
||||
js-cookie: 3.0.1
|
||||
jsonwebtoken: 8.5.1
|
||||
marked: 4.2.2
|
||||
next: 13.0.3-canary.2_biqbaboplfbrettd7655fr4n2y
|
||||
next-themes: /@wits/next-themes/0.2.7_qjr36eup74ongf7bl2iopfchwe
|
||||
prism-react-renderer: 1.3.5_react@18.2.0
|
||||
rc-table: 7.24.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
||||
|
@ -56,12 +71,16 @@ dependencies:
|
|||
react-loading-skeleton: 3.1.0_react@18.2.0
|
||||
swr: 1.3.0_react@18.2.0
|
||||
textarea-markdown-editor: 0.1.13_biqbaboplfbrettd7655fr4n2y
|
||||
zod: 3.19.1
|
||||
|
||||
optionalDependencies:
|
||||
sharp: 0.31.2
|
||||
|
||||
devDependencies:
|
||||
'@next/bundle-analyzer': 12.1.6
|
||||
'@types/bcrypt': 5.0.0
|
||||
'@types/jsonwebtoken': 8.5.9
|
||||
'@types/marked': 4.0.7
|
||||
'@types/node': 17.0.23
|
||||
'@types/react': 18.0.9
|
||||
'@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y
|
||||
|
@ -71,6 +90,7 @@ devDependencies:
|
|||
eslint-config-next: 13.0.2_hsmo2rtalirsvadpuxki35bq2i
|
||||
next-unused: 0.0.6
|
||||
prettier: 2.6.2
|
||||
prisma: 4.6.0
|
||||
typescript: 4.6.4
|
||||
typescript-plugin-css-modules: 3.4.0_typescript@4.6.4
|
||||
|
||||
|
@ -175,6 +195,24 @@ packages:
|
|||
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
||||
dev: true
|
||||
|
||||
/@mapbox/node-pre-gyp/1.0.10:
|
||||
resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
detect-libc: 2.0.1
|
||||
https-proxy-agent: 5.0.1
|
||||
make-dir: 3.1.0
|
||||
node-fetch: 2.6.7
|
||||
nopt: 5.0.0
|
||||
npmlog: 5.0.1
|
||||
rimraf: 3.0.2
|
||||
semver: 7.3.8
|
||||
tar: 6.1.12
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@next/bundle-analyzer/12.1.6:
|
||||
resolution: {integrity: sha512-WLydwytAeHoC/neXsiIgK+a6Me12PuSpwopnsZgX5JFNwXQ9MlwPeMGS3aTZkYsv8QmSm0Ns9Yh9FkgLKYaUuQ==}
|
||||
dependencies:
|
||||
|
@ -339,6 +377,28 @@ 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==}
|
||||
engines: {node: '>=14.17'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
prisma: '*'
|
||||
peerDependenciesMeta:
|
||||
prisma:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@prisma/engines-version': 4.6.0-53.2e719efb80b56a3f32d18a62489de95bb9c130e3
|
||||
prisma: 4.6.0
|
||||
dev: false
|
||||
|
||||
/@prisma/engines-version/4.6.0-53.2e719efb80b56a3f32d18a62489de95bb9c130e3:
|
||||
resolution: {integrity: sha512-0CTnfEuUbLlO6n1fM89ERDbSwI4LoyZn+1OKVSwG+aVqohj34+mXRfwOWIM0ONtYtLGGBpddvQAnAZkg+cgS6g==}
|
||||
dev: false
|
||||
|
||||
/@prisma/engines/4.6.0:
|
||||
resolution: {integrity: sha512-S+72PAl0zTCbIGou1uXD/McvzdtP+bjOs0LRmGZfcOQcVqR9x/0f6Z+dqpUU0zIcqHEl+0DOB8UXaTwRvssFsQ==}
|
||||
requiresBuild: true
|
||||
|
||||
/@rushstack/eslint-patch/1.2.0:
|
||||
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
|
||||
dev: true
|
||||
|
@ -349,6 +409,12 @@ packages:
|
|||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@types/bcrypt/5.0.0:
|
||||
resolution: {integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.23
|
||||
dev: true
|
||||
|
||||
/@types/cookie/0.5.1:
|
||||
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
|
||||
dev: false
|
||||
|
@ -361,6 +427,16 @@ packages:
|
|||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
dev: true
|
||||
|
||||
/@types/jsonwebtoken/8.5.9:
|
||||
resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.23
|
||||
dev: true
|
||||
|
||||
/@types/marked/4.0.7:
|
||||
resolution: {integrity: sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==}
|
||||
dev: true
|
||||
|
||||
/@types/node/17.0.23:
|
||||
resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==}
|
||||
dev: true
|
||||
|
@ -507,6 +583,10 @@ packages:
|
|||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/abbrev/1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
dev: false
|
||||
|
||||
/acorn-jsx/5.3.2_acorn@8.8.1:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
|
@ -526,6 +606,15 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/agent-base/6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/ajv/6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
dependencies:
|
||||
|
@ -538,7 +627,6 @@ packages:
|
|||
/ansi-regex/5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/ansi-styles/3.2.1:
|
||||
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
|
||||
|
@ -566,6 +654,18 @@ packages:
|
|||
resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==}
|
||||
dev: true
|
||||
|
||||
/aproba/2.0.0:
|
||||
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
|
||||
dev: false
|
||||
|
||||
/are-we-there-yet/2.0.0:
|
||||
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
delegates: 1.0.0
|
||||
readable-stream: 3.6.0
|
||||
dev: false
|
||||
|
||||
/argparse/2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: true
|
||||
|
@ -649,11 +749,22 @@ packages:
|
|||
|
||||
/balanced-match/1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/base64-js/1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
/bcrypt/5.1.0:
|
||||
resolution: {integrity: sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.10
|
||||
node-addon-api: 5.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/big.js/3.2.0:
|
||||
resolution: {integrity: sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==}
|
||||
dev: true
|
||||
|
@ -679,7 +790,6 @@ packages:
|
|||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: true
|
||||
|
||||
/braces/3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
|
@ -688,6 +798,10 @@ packages:
|
|||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/buffer-equal-constant-time/1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
dev: false
|
||||
|
||||
/buffer/5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
dependencies:
|
||||
|
@ -747,6 +861,11 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/chownr/2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/classnames/2.3.2:
|
||||
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
|
||||
dev: false
|
||||
|
@ -808,6 +927,11 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/color-support/1.1.3:
|
||||
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/color/4.2.3:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
|
@ -832,7 +956,10 @@ packages:
|
|||
|
||||
/concat-map/0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/console-control-strings/1.1.0:
|
||||
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
||||
dev: false
|
||||
|
||||
/cookie/0.5.0:
|
||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||
|
@ -954,7 +1081,6 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/decode-uri-component/0.2.0:
|
||||
resolution: {integrity: sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==}
|
||||
|
@ -991,6 +1117,10 @@ packages:
|
|||
object-keys: 1.1.1
|
||||
dev: true
|
||||
|
||||
/delegates/1.0.0:
|
||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||
dev: false
|
||||
|
||||
/dependency-tree/8.1.2:
|
||||
resolution: {integrity: sha512-c4CL1IKxkKng0oT5xrg4uNiiMVFqTGOXqHSFx7XEFdgSsp6nw3AGGruICppzJUrfad/r7GLqt26rmWU4h4j39A==}
|
||||
engines: {node: ^10.13 || ^12 || >=14}
|
||||
|
@ -1009,7 +1139,6 @@ packages:
|
|||
resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/detective-amd/3.1.2:
|
||||
resolution: {integrity: sha512-jffU26dyqJ37JHR/o44La6CxtrDf3Rt9tvd2IbImJYxWKTMdBjctp37qoZ6ZcY80RHg+kzWz4bXn39e4P7cctQ==}
|
||||
|
@ -1139,6 +1268,16 @@ packages:
|
|||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
dev: true
|
||||
|
||||
/ecdsa-sig-formatter/1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/emoji-regex/8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
dev: false
|
||||
|
||||
/emoji-regex/9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
dev: true
|
||||
|
@ -1646,9 +1785,15 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/fs-minipass/2.1.0:
|
||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
minipass: 3.3.4
|
||||
dev: false
|
||||
|
||||
/fs.realpath/1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
||||
/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
|
@ -1676,6 +1821,21 @@ packages:
|
|||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||
dev: true
|
||||
|
||||
/gauge/3.0.2:
|
||||
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
aproba: 2.0.0
|
||||
color-support: 1.1.3
|
||||
console-control-strings: 1.1.0
|
||||
has-unicode: 2.0.1
|
||||
object-assign: 4.1.1
|
||||
signal-exit: 3.0.7
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wide-align: 1.1.5
|
||||
dev: false
|
||||
|
||||
/generic-names/1.0.3:
|
||||
resolution: {integrity: sha512-b6OHfQuKasIKM9b6YPkX+KUj/TLBTx3B/1aT1T5F12FEuEqyFMdr59OMS53aoaSw8eVtapdqieX6lbg5opaOhA==}
|
||||
dependencies:
|
||||
|
@ -1749,7 +1909,6 @@ packages:
|
|||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: true
|
||||
|
||||
/globals/13.17.0:
|
||||
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==}
|
||||
|
@ -1838,6 +1997,10 @@ packages:
|
|||
has-symbols: 1.0.3
|
||||
dev: true
|
||||
|
||||
/has-unicode/2.0.1:
|
||||
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
|
||||
dev: false
|
||||
|
||||
/has/1.0.3:
|
||||
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
@ -1845,6 +2008,16 @@ packages:
|
|||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/https-proxy-agent/5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/iconv-lite/0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -1910,7 +2083,6 @@ packages:
|
|||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/inherits/2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
@ -1976,6 +2148,11 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-fullwidth-code-point/3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/is-glob/4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -2118,6 +2295,22 @@ packages:
|
|||
minimist: 1.2.7
|
||||
dev: true
|
||||
|
||||
/jsonwebtoken/8.5.1:
|
||||
resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==}
|
||||
engines: {node: '>=4', npm: '>=1.4.28'}
|
||||
dependencies:
|
||||
jws: 3.2.2
|
||||
lodash.includes: 4.3.0
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isinteger: 4.0.4
|
||||
lodash.isnumber: 3.0.3
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.isstring: 4.0.1
|
||||
lodash.once: 4.1.1
|
||||
ms: 2.1.3
|
||||
semver: 5.7.1
|
||||
dev: false
|
||||
|
||||
/jsx-ast-utils/3.3.3:
|
||||
resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
@ -2126,6 +2319,21 @@ packages:
|
|||
object.assign: 4.1.4
|
||||
dev: true
|
||||
|
||||
/jwa/1.4.1:
|
||||
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/jws/3.2.2:
|
||||
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
|
||||
dependencies:
|
||||
jwa: 1.4.1
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/language-subtag-registry/0.3.22:
|
||||
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
|
||||
dev: true
|
||||
|
@ -2206,10 +2414,38 @@ packages:
|
|||
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||
dev: true
|
||||
|
||||
/lodash.includes/4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||
dev: false
|
||||
|
||||
/lodash.isboolean/3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||
dev: false
|
||||
|
||||
/lodash.isinteger/4.0.4:
|
||||
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isnumber/3.0.3:
|
||||
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||
dev: false
|
||||
|
||||
/lodash.isplainobject/4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isstring/4.0.1:
|
||||
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge/4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash.once/4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
dev: false
|
||||
|
||||
/lodash/4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
|
@ -2275,6 +2511,19 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/make-dir/3.1.0:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
semver: 6.3.0
|
||||
dev: false
|
||||
|
||||
/marked/4.2.2:
|
||||
resolution: {integrity: sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==}
|
||||
engines: {node: '>= 12'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/memory-fs/0.5.0:
|
||||
resolution: {integrity: sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==}
|
||||
engines: {node: '>=4.3.0 <5.0.0 || >=5.10'}
|
||||
|
@ -2319,11 +2568,25 @@ packages:
|
|||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/minimist/1.2.7:
|
||||
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
|
||||
|
||||
/minipass/3.3.4:
|
||||
resolution: {integrity: sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/minizlib/2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
minipass: 3.3.4
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/mkdirp-classic/0.5.3:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
dev: false
|
||||
|
@ -2333,7 +2596,6 @@ packages:
|
|||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/module-definition/3.4.0:
|
||||
resolution: {integrity: sha512-XxJ88R1v458pifaSkPNLUTdSPNVGMP2SXVncVmApGO+gAfrLANiYe6JofymCzVceGOMwQE2xogxBSc8uB7XegA==}
|
||||
|
@ -2373,11 +2635,9 @@ packages:
|
|||
|
||||
/ms/2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/ms/2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: true
|
||||
|
||||
/nanoid/3.3.4:
|
||||
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||
|
@ -2474,7 +2734,18 @@ packages:
|
|||
/node-addon-api/5.0.0:
|
||||
resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==}
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/node-fetch/2.6.7:
|
||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: false
|
||||
|
||||
/node-source-walk/4.3.0:
|
||||
resolution: {integrity: sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA==}
|
||||
|
@ -2483,11 +2754,28 @@ packages:
|
|||
'@babel/parser': 7.20.3
|
||||
dev: true
|
||||
|
||||
/nopt/5.0.0:
|
||||
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
abbrev: 1.1.1
|
||||
dev: false
|
||||
|
||||
/normalize-path/3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/npmlog/5.0.1:
|
||||
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
|
||||
dependencies:
|
||||
are-we-there-yet: 2.0.0
|
||||
console-control-strings: 1.1.0
|
||||
gauge: 3.0.2
|
||||
set-blocking: 2.0.0
|
||||
dev: false
|
||||
|
||||
/object-assign/4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -2640,7 +2928,6 @@ packages:
|
|||
/path-is-absolute/1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/path-key/3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
|
@ -2843,6 +3130,22 @@ packages:
|
|||
parse-ms: 2.1.0
|
||||
dev: true
|
||||
|
||||
/prism-react-renderer/1.3.5_react@18.2.0:
|
||||
resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==}
|
||||
peerDependencies:
|
||||
react: '>=0.14.9'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/prisma/4.6.0:
|
||||
resolution: {integrity: sha512-TAnObUMGCM9NLt9nsRs1WWYQGPKsJOK8bN/7gSAnBcYIxMCFFDe+XtFYJbyTzsJZ/i+0rH4zg8au3m7HX354LA==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@prisma/engines': 4.6.0
|
||||
|
||||
/process-nextick-args/2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
dev: true
|
||||
|
@ -3142,7 +3445,6 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
dev: true
|
||||
|
||||
/run-parallel/1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
@ -3199,13 +3501,10 @@ packages:
|
|||
/semver/5.7.1:
|
||||
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/semver/6.3.0:
|
||||
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/semver/7.3.8:
|
||||
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
|
||||
|
@ -3214,6 +3513,10 @@ packages:
|
|||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
/set-blocking/2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
dev: false
|
||||
|
||||
/shallowequal/1.1.0:
|
||||
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
|
||||
dev: false
|
||||
|
@ -3256,7 +3559,6 @@ packages:
|
|||
|
||||
/signal-exit/3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
dev: true
|
||||
|
||||
/simple-concat/1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
|
@ -3323,6 +3625,15 @@ packages:
|
|||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/string-width/4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
dev: false
|
||||
|
||||
/string.prototype.matchall/4.0.8:
|
||||
resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==}
|
||||
dependencies:
|
||||
|
@ -3377,7 +3688,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
dev: true
|
||||
|
||||
/strip-bom/3.0.0:
|
||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||
|
@ -3496,6 +3806,18 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/tar/6.1.12:
|
||||
resolution: {integrity: sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
chownr: 2.0.0
|
||||
fs-minipass: 2.1.0
|
||||
minipass: 3.3.4
|
||||
minizlib: 2.1.2
|
||||
mkdirp: 1.0.4
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/temp/0.4.0:
|
||||
resolution: {integrity: sha512-IsFisGgDKk7qzK9erMIkQe/XwiSUdac7z3wYOsjcLkhPBy3k1SlvLoIh2dAHIlEpgA971CgguMrx9z8fFg7tSA==}
|
||||
engines: {'0': node >=0.4.0}
|
||||
|
@ -3534,6 +3856,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/tr46/0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
dev: false
|
||||
|
||||
/ts-loader/7.0.5_typescript@4.6.4:
|
||||
resolution: {integrity: sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
@ -3697,6 +4023,10 @@ packages:
|
|||
defaults: 1.0.4
|
||||
dev: true
|
||||
|
||||
/webidl-conversions/3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: false
|
||||
|
||||
/webpack-bundle-analyzer/4.3.0:
|
||||
resolution: {integrity: sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
@ -3716,6 +4046,13 @@ packages:
|
|||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/whatwg-url/5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
dev: false
|
||||
|
||||
/which-boxed-primitive/1.0.2:
|
||||
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
|
||||
dependencies:
|
||||
|
@ -3734,6 +4071,12 @@ packages:
|
|||
isexe: 2.0.0
|
||||
dev: true
|
||||
|
||||
/wide-align/1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
dev: false
|
||||
|
||||
/word-wrap/1.2.3:
|
||||
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -3767,3 +4110,7 @@ packages:
|
|||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/zod/3.19.1:
|
||||
resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==}
|
||||
dev: false
|
||||
|
|
73
client/prisma/migrations/20221110002714_init/migration.sql
Normal file
73
client/prisma/migrations/20221110002714_init/migration.sql
Normal file
|
@ -0,0 +1,73 @@
|
|||
-- 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");
|
|
@ -0,0 +1,19 @@
|
|||
-- 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;
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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;
|
3
client/prisma/migrations/migration_lock.toml
Normal file
3
client/prisma/migrations/migration_lock.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
93
client/prisma/schema.prisma
Normal file
93
client/prisma/schema.prisma
Normal file
|
@ -0,0 +1,93 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model AuthTokens {
|
||||
id String @default(cuid())
|
||||
token String
|
||||
expiredReason String?
|
||||
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)
|
||||
|
||||
@@id([id, token])
|
||||
// make id and token keys
|
||||
@@unique([id, token])
|
||||
}
|
||||
|
||||
model SequelizeMeta {
|
||||
name String @id
|
||||
}
|
||||
|
||||
model File {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
content String
|
||||
sha String
|
||||
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)
|
||||
|
||||
@@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 {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
visibility String
|
||||
password String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
expiresAt DateTime?
|
||||
parentId String?
|
||||
description String?
|
||||
// files File[]
|
||||
// postToAuthors PostToAuthors[]
|
||||
|
||||
@@map("Posts")
|
||||
}
|
||||
|
||||
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[]
|
||||
|
||||
@@map("Users")
|
||||
}
|
Loading…
Reference in a new issue