start api transition, prisma additions

This commit is contained in:
Max Leiter 2022-11-09 18:38:05 -08:00
parent da870d6957
commit 9b9c3c1d87
82 changed files with 1671 additions and 202 deletions

View file

@ -1,2 +0,0 @@
API_URL=http://localhost:3000
SECRET_KEY=secret

View file

@ -0,0 +1,5 @@
import PageSeo from "@components/head"
export default function AuthHead() {
return <PageSeo title="Sign In" />
}

View file

@ -0,0 +1,5 @@
import Auth from "@components/auth"
export default function SignInPage() {
return <Auth page="signin" />
}

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

View 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

View file

@ -0,0 +1,5 @@
import PageSeo from "@components/page-seo"
export default function NewPostHead() {
return <PageSeo title="Create a new Drift" />
}

View file

@ -0,0 +1,10 @@
import NewPost from "@components/new-post"
import '@styles/react-datepicker.css'
const New = () => {
return (
<NewPost />
)
}
export default New

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { Page } from "@geist-ui/core"
import { Page } from "@geist-ui/core/dist"
const Error = ({ status }: { status: number }) => {
return (

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@
}
.error {
background: red;
background: #f33;
}
.type {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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
}

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

View 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
View 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)

View file

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

View file

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

View 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">
&#8203;
<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 }} />
</>
</>
)
}

View file

@ -65,7 +65,7 @@ export function middleware(req: NextRequest, event: NextFetchEvent) {
export const config = {
match: [
"/signout",
"/",
// "/",
"/signin",
"/signup",
"/new",

View file

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

View file

@ -1,4 +1,4 @@
import { CssBaseline } from "@geist-ui/core"
import { CssBaseline } from "@geist-ui/core/dist"
import Document, {
Html,
Head,

View file

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

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

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

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

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

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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");

View file

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

View file

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

View 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"

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