diff --git a/client/app/(posts)/new/components/edit-document-list/edit-document/document.module.css b/client/app/(posts)/new/components/edit-document-list/edit-document/document.module.css index 99c75125..cbc50f95 100644 --- a/client/app/(posts)/new/components/edit-document-list/edit-document/document.module.css +++ b/client/app/(posts)/new/components/edit-document-list/edit-document/document.module.css @@ -1,5 +1,4 @@ .card { - margin: var(--gap) auto; padding: var(--gap); border: 1px solid var(--lighter-gray); border-radius: var(--radius); diff --git a/client/app/(posts)/new/components/new.tsx b/client/app/(posts)/new/components/new.tsx index 689d5d2a..ebdc2ba5 100644 --- a/client/app/(posts)/new/components/new.tsx +++ b/client/app/(posts)/new/components/new.tsx @@ -252,7 +252,7 @@ const Post = ({ ) return ( -
+
<Description description={description} onChange={onChangeDescription} /> <FileDropzone setDocs={uploadDocs} /> diff --git a/client/app/(posts)/new/components/post.module.css b/client/app/(posts)/new/components/post.module.css index 22c7f6d0..7d423d34 100644 --- a/client/app/(posts)/new/components/post.module.css +++ b/client/app/(posts)/new/components/post.module.css @@ -1,3 +1,10 @@ +.root { + padding-bottom: 200px; + display: flex; + flex-direction: column; + gap: var(--gap); +} + .buttons { position: relative; display: flex; @@ -19,7 +26,6 @@ .description { width: 100%; - margin-bottom: var(--gap); } @media screen and (max-width: 650px) { diff --git a/client/app/(posts)/new/components/title/index.tsx b/client/app/(posts)/new/components/title/index.tsx index 3e32b8d7..fbc3714f 100644 --- a/client/app/(posts)/new/components/title/index.tsx +++ b/client/app/(posts)/new/components/title/index.tsx @@ -23,7 +23,7 @@ type props = { const Title = ({ onChange, title }: props) => { return ( <div className={styles.title}> - <h1>Drift</h1> + <h1 style={{ margin: 0, padding: 0 }}>Drift</h1> <Input placeholder={placeholder} value={title} diff --git a/client/app/(posts)/new/components/title/title.module.css b/client/app/(posts)/new/components/title/title.module.css index 03d21077..09668462 100644 --- a/client/app/(posts)/new/components/title/title.module.css +++ b/client/app/(posts)/new/components/title/title.module.css @@ -3,7 +3,7 @@ flex-direction: row; align-items: center; justify-content: space-between; - margin-bottom: var(--gap); + gap: inherit; } @media screen and (max-width: 650px) { diff --git a/client/app/(posts)/post/[id]/components/header/title/index.tsx b/client/app/(posts)/post/[id]/components/header/title/index.tsx index f2babd47..b7997037 100644 --- a/client/app/(posts)/post/[id]/components/header/title/index.tsx +++ b/client/app/(posts)/post/[id]/components/header/title/index.tsx @@ -10,8 +10,8 @@ type TitleProps = { loading?: boolean displayName?: string visibility?: string - createdAt?: Date - expiresAt?: Date + createdAt?: string + expiresAt?: string authorId?: string } diff --git a/client/app/(posts)/post/[id]/components/post-page/view-document/document.module.css b/client/app/(posts)/post/[id]/components/post-page/view-document/document.module.css index 55fa9025..866dec3e 100644 --- a/client/app/(posts)/post/[id]/components/post-page/view-document/document.module.css +++ b/client/app/(posts)/post/[id]/components/post-page/view-document/document.module.css @@ -1,5 +1,4 @@ .card { - margin: var(--gap) auto; padding: var(--gap); border: 1px solid var(--light-gray); border-radius: var(--radius); diff --git a/client/app/(posts)/post/[id]/components/post-page/view-document/index.tsx b/client/app/(posts)/post/[id]/components/post-page/view-document/index.tsx index 78831529..085248e9 100644 --- a/client/app/(posts)/post/[id]/components/post-page/view-document/index.tsx +++ b/client/app/(posts)/post/[id]/components/post-page/view-document/index.tsx @@ -20,7 +20,7 @@ type Props = { preview: string } -const DownloadButton = ({ rawLink }: { rawLink?: string }) => { +const DownloadButtons = ({ rawLink }: { rawLink?: string }) => { return ( <div className={styles.actionWrapper}> <ButtonGroup className={styles.actions}> @@ -59,12 +59,6 @@ const Document = ({ skeleton, id }: Props) => { - const rawLink = () => { - if (id) { - return `/file/raw/${id}` - } - } - if (skeleton) { return ( <> @@ -98,7 +92,7 @@ const Document = ({ /> </Link> <div className={styles.descriptionContainer}> - <DownloadButton rawLink={rawLink()} /> + <DownloadButtons rawLink={`/api/file/raw/${id}`} /> <DocumentTabs defaultTab={initialTab} preview={preview} diff --git a/client/app/(posts)/post/[id]/page.tsx b/client/app/(posts)/post/[id]/page.tsx index 273d9ae1..64276c08 100644 --- a/client/app/(posts)/post/[id]/page.tsx +++ b/client/app/(posts)/post/[id]/page.tsx @@ -57,6 +57,7 @@ const getPost = async (id: string) => { if (post.visibility === "protected" && !isAuthorOrAdmin) { return { + // TODO: remove this. It's temporary to appease typescript post: { visibility: "protected", id: post.id, @@ -64,6 +65,7 @@ const getPost = async (id: string) => { parentId: "", title: "", createdAt: new Date("1970-01-01"), + expiresAt: new Date("1970-01-01"), author: { displayName: "", }, @@ -108,7 +110,8 @@ const PostView = async ({ /> <PostTitle title={post.title} - createdAt={post.createdAt} + createdAt={post.createdAt.toString()} + expiresAt={post.expiresAt?.toString()} displayName={post.author?.displayName || ""} visibility={post.visibility} authorId={post.authorId} diff --git a/client/app/components/header/header.module.css b/client/app/components/header/header.module.css index 7de531e5..d6b4ae9b 100644 --- a/client/app/components/header/header.module.css +++ b/client/app/components/header/header.module.css @@ -45,7 +45,6 @@ opacity: 1; } - .selectContent { width: auto; height: 18px; @@ -64,6 +63,10 @@ display: none; } +.contentWrapper { + background: var(--bg); +} + @media only screen and (max-width: 768px) { .wrapper [data-tab="github"] { display: none; @@ -71,6 +74,7 @@ .mobile { margin-top: var(--gap); + margin-bottom: var(--gap); display: flex; } @@ -79,6 +83,23 @@ flex-direction: column; } + .dropdownItem:not(:first-child):not(:last-child) :global(button) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + .dropdownItem:first-child :global(button) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + .dropdownItem:last-child :global(button) { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + .dropdownItem a, .dropdownItem button { width: 100%; diff --git a/client/app/components/header/index.tsx b/client/app/components/header/index.tsx index dfb5be69..b26f11b7 100644 --- a/client/app/components/header/index.tsx +++ b/client/app/components/header/index.tsx @@ -22,7 +22,7 @@ import { } from "react-feather" import * as DropdownMenu from "@radix-ui/react-dropdown-menu" import buttonStyles from "@components/button/button.module.css" -import { useEffect, useMemo, useState } from "react" +import { useMemo } from "react" type Tab = { name: string @@ -155,17 +155,13 @@ const Header = () => { ] }, [isAdmin, resolvedTheme, isSignedIn, setTheme]) - // // TODO: this should not be necessary. - // if (!clientHydrated) { - // return ( - // <header> - // <div className={styles.tabs}>{getPages(true).map(getButton)}</div> - // </header> - // ) - // } - const buttons = pages.map(getButton) + // TODO: this is a hack to close the radix ui menu when a next link is clicked + const onClick = () => { + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); + } + return ( <header className={clsx(styles.header, { [styles.loading]: isLoading, @@ -188,6 +184,7 @@ const Header = () => { <DropdownMenu.Item key={button?.key} className={styles.dropdownItem} + onClick={onClick} > {button} </DropdownMenu.Item> diff --git a/client/app/components/input/index.tsx b/client/app/components/input/index.tsx index 7f7185d4..f5ad75b1 100644 --- a/client/app/components/input/index.tsx +++ b/client/app/components/input/index.tsx @@ -2,15 +2,6 @@ import clsx from "clsx" import React from "react" import styles from "./input.module.css" -type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick< - T, - Exclude<keyof T, Keys> -> & - { - [K in Keys]-?: Required<Pick<T, K>> & - Partial<Record<Exclude<Keys, K>, undefined>> - }[Keys] - type Props = React.HTMLProps<HTMLInputElement> & { label?: string width?: number | string @@ -18,8 +9,30 @@ type Props = React.HTMLProps<HTMLInputElement> & { labelClassName?: string } -type InputProps = RequireOnlyOne<Props, "label" | "aria-label"> - +// we have two special rules on top of the props: +// if onChange or value is passed, we require both +// if label is passed, we forbid aria-label and vice versa +type InputProps = Omit<Props, "onChange" | "value" | "label" | "aria-label"> & + ( + | { + onChange: Props["onChange"] + value: Props["value"] + } // if onChange or value is passed, we require both + | { + onChange?: never + value?: never + } + ) & + ( + | { + label: Props["label"] + "aria-label"?: never + } // if label is passed, we forbid aria-label and vice versa + | { + label?: never + "aria-label": Props["aria-label"] + } + ) // eslint-disable-next-line react/display-name const Input = React.forwardRef<HTMLInputElement, InputProps>( ({ label, className, width, height, labelClassName, ...props }, ref) => { diff --git a/client/app/components/input/input.module.css b/client/app/components/input/input.module.css index bc97a4b4..eb147988 100644 --- a/client/app/components/input/input.module.css +++ b/client/app/components/input/input.module.css @@ -51,12 +51,6 @@ white-space: nowrap; } -@media screen and (max-width: 768px) { - .wrapper { - margin-bottom: var(--gap); - } -} - .input:disabled { background-color: var(--lighter-gray); color: var(--fg); diff --git a/client/app/components/post-list/index.tsx b/client/app/components/post-list/index.tsx index 60e6b1f8..e8838c0d 100644 --- a/client/app/components/post-list/index.tsx +++ b/client/app/components/post-list/index.tsx @@ -2,14 +2,20 @@ import styles from "./post-list.module.css" import ListItem from "./list-item" -import { ChangeEvent, useCallback, useEffect, useState } from "react" -import useDebounce from "@lib/hooks/use-debounce" +import { + ChangeEvent, + useCallback, + useDeferredValue, + useEffect, + useState +} from "react" import Link from "@components/link" import type { PostWithFiles } from "@lib/server/prisma" import Input from "@components/input" import Button from "@components/button" import { useToasts } from "@components/toasts" import { ListItemSkeleton } from "./list-item-skeleton" +import debounce from "lodash.debounce" type Props = { initialPosts: string | PostWithFiles[] @@ -32,8 +38,6 @@ const PostList = ({ const [hasMorePosts, setHasMorePosts] = useState(morePosts) const { setToast } = useToasts() - const debouncedSearchValue = useDebounce(search, 200) - const loadMoreClick = useCallback( (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault() @@ -56,36 +60,30 @@ const PostList = ({ [posts, hasMorePosts] ) - // update posts on search - useEffect(() => { - if (debouncedSearchValue) { - setSearching(true) - async function fetchPosts() { - const res = await fetch( - `/api/post/search?q=${encodeURIComponent( - debouncedSearchValue - )}&userId=${userId}`, - { - method: "GET", - headers: { - "Content-Type": "application/json" - } + const onSearch = (query: string) => { + setSearching(true) + async function fetchPosts() { + const res = await fetch( + `/api/post/search?q=${encodeURIComponent(query)}&userId=${userId}`, + { + method: "GET", + headers: { + "Content-Type": "application/json" } - ) - const json = await res.json() - setPosts(json) - setSearching(false) - } - fetchPosts() - } else { - setPosts(initialPosts) + } + ) + const json = await res.json() + setPosts(json) + setSearching(false) } - // TODO: fix cyclical dependency issue - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [debouncedSearchValue, userId]) + fetchPosts() + } + + const debouncedSearch = debounce(onSearch, 500) const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => { setSearchValue(e.target.value) + debouncedSearch(e.target.value) } const deletePost = useCallback( @@ -117,10 +115,11 @@ const PostList = ({ disabled={!posts} style={{ maxWidth: 300 }} aria-label="Search" + value={search} /> </div> {!posts && <p style={{ color: "var(--warning)" }}>Failed to load.</p>} - {!posts?.length && ( + {searching && ( <ul> <ListItemSkeleton /> <ListItemSkeleton /> diff --git a/client/package.json b/client/package.json index e2ff4132..7c0a70d6 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,7 @@ "dependencies": { "@next-auth/prisma-adapter": "^1.0.5", "@next/eslint-plugin-next": "13.0.5-canary.3", - "@prisma/client": "^4.6.1", + "@prisma/client": "^4.7.1", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.1", "@radix-ui/react-popover": "^1.0.2", @@ -26,6 +26,7 @@ "bcrypt": "^5.1.0", "client-zip": "2.2.1", "jest": "^29.3.1", + "lodash.debounce": "^4.0.8", "next": "13.0.7-canary.1", "next-auth": "^4.18.0", "prisma": "^4.7.1", @@ -41,6 +42,7 @@ "devDependencies": { "@next/bundle-analyzer": "12.1.6", "@types/bcrypt": "^5.0.0", + "@types/lodash.debounce": "^4.0.7", "@types/node": "17.0.23", "@types/react": "18.0.9", "@types/react-datepicker": "4.4.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index c0ce9c79..99934ec6 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -4,13 +4,14 @@ specifiers: '@next-auth/prisma-adapter': ^1.0.5 '@next/bundle-analyzer': 12.1.6 '@next/eslint-plugin-next': 13.0.5-canary.3 - '@prisma/client': ^4.6.1 + '@prisma/client': ^4.7.1 '@radix-ui/react-dialog': ^1.0.2 '@radix-ui/react-dropdown-menu': ^2.0.1 '@radix-ui/react-popover': ^1.0.2 '@radix-ui/react-tabs': ^1.0.1 '@radix-ui/react-tooltip': ^1.0.2 '@types/bcrypt': ^5.0.0 + '@types/lodash.debounce': ^4.0.7 '@types/node': 17.0.23 '@types/react': 18.0.9 '@types/react-datepicker': 4.4.1 @@ -25,6 +26,7 @@ specifiers: eslint: 8.27.0 eslint-config-next: 13.0.3 jest: ^29.3.1 + lodash.debounce: ^4.0.8 next: 13.0.7-canary.1 next-auth: ^4.18.0 next-unused: 0.0.6 @@ -43,9 +45,9 @@ specifiers: typescript-plugin-css-modules: 3.4.0 dependencies: - '@next-auth/prisma-adapter': 1.0.5_qwexivae5olc6wqfcmxswm7qjy + '@next-auth/prisma-adapter': 1.0.5_hpttyne5hky44pj2anoxcmv4zm '@next/eslint-plugin-next': 13.0.5-canary.3 - '@prisma/client': 4.6.1_prisma@4.7.1 + '@prisma/client': 4.7.1_prisma@4.7.1 '@radix-ui/react-dialog': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-dropdown-menu': 2.0.1_jbvntnid6ohjelon6ccj5dhg2u '@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u @@ -56,6 +58,7 @@ dependencies: bcrypt: 5.1.0 client-zip: 2.2.1 jest: 29.3.1_@types+node@17.0.23 + lodash.debounce: 4.0.8 next: 13.0.7-canary.1_biqbaboplfbrettd7655fr4n2y next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq prisma: 4.7.1 @@ -74,6 +77,7 @@ optionalDependencies: devDependencies: '@next/bundle-analyzer': 12.1.6 '@types/bcrypt': 5.0.0 + '@types/lodash.debounce': 4.0.7 '@types/node': 17.0.23 '@types/react': 18.0.9 '@types/react-datepicker': 4.4.1_biqbaboplfbrettd7655fr4n2y @@ -785,13 +789,13 @@ packages: - supports-color dev: false - /@next-auth/prisma-adapter/1.0.5_qwexivae5olc6wqfcmxswm7qjy: + /@next-auth/prisma-adapter/1.0.5_hpttyne5hky44pj2anoxcmv4zm: resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==} peerDependencies: '@prisma/client': '>=2.26.0 || >=3' next-auth: ^4 dependencies: - '@prisma/client': 4.6.1_prisma@4.7.1 + '@prisma/client': 4.7.1_prisma@4.7.1 next-auth: 4.18.0_ihvxcpofhpc4k2aqfys2drrlkq dev: false @@ -969,8 +973,8 @@ packages: /@popperjs/core/2.11.6: resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} - /@prisma/client/4.6.1_prisma@4.7.1: - resolution: {integrity: sha512-M1+NNrMzqaOIxT7PBGcTs3IZo7d1EW/+gVQd4C4gUgWBDGgD9AcIeZnUSidgWClmpMSgVUdnVORjsWWGUameYA==} + /@prisma/client/4.7.1_prisma@4.7.1: + resolution: {integrity: sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==} engines: {node: '>=14.17'} requiresBuild: true peerDependencies: @@ -979,12 +983,12 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32 + '@prisma/engines-version': 4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c prisma: 4.7.1 dev: false - /@prisma/engines-version/4.6.1-3.694eea289a8462c80264df36757e4fdc129b1b32: - resolution: {integrity: sha512-HUCmkXAU2jqp2O1RvNtbE+seLGLyJGEABZS/R38rZjSAafAy0WzBuHq+tbZMnD+b5OSCsTVtIPVcuvx1ySxcWQ==} + /@prisma/engines-version/4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c: + resolution: {integrity: sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==} dev: false /@prisma/engines/4.7.1: @@ -1519,6 +1523,16 @@ packages: resolution: {integrity: sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==} dev: false + /@types/lodash.debounce/4.0.7: + resolution: {integrity: sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==} + dependencies: + '@types/lodash': 4.14.191 + dev: true + + /@types/lodash/4.14.191: + resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} + dev: true + /@types/mdast/3.0.10: resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} dependencies: @@ -4584,6 +4598,10 @@ packages: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true + /lodash.debounce/4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: false + /lodash.memoize/4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} dev: false diff --git a/client/prisma/migrations/20221112095706_init/migration.sql b/client/prisma/migrations/20221112095706_init/migration.sql deleted file mode 100644 index 9172c0b7..00000000 --- a/client/prisma/migrations/20221112095706_init/migration.sql +++ /dev/null @@ -1,109 +0,0 @@ --- CreateTable -CREATE TABLE "SequelizeMeta" ( - "name" TEXT NOT NULL, - - CONSTRAINT "SequelizeMeta_pkey" PRIMARY KEY ("name") -); - --- CreateTable -CREATE TABLE "files" ( - "id" TEXT NOT NULL, - "title" TEXT NOT NULL, - "content" TEXT NOT NULL, - "sha" TEXT NOT NULL, - "html" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - "deletedAt" TIMESTAMP(3), - "userId" TEXT NOT NULL, - "postId" TEXT NOT NULL, - - CONSTRAINT "files_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "posts" ( - "id" TEXT NOT NULL, - "title" TEXT NOT NULL, - "visibility" TEXT NOT NULL, - "password" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - "deletedAt" TIMESTAMP(3), - "expiresAt" TIMESTAMP(3), - "parentId" TEXT, - "description" TEXT, - "authorId" TEXT NOT NULL, - - CONSTRAINT "posts_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "accounts" ( - "id" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "provider" TEXT NOT NULL, - "providerAccountId" TEXT NOT NULL, - "refresh_token" TEXT, - "access_token" TEXT, - "expires_at" INTEGER, - "token_type" TEXT, - "scope" TEXT, - "id_token" TEXT, - "session_state" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "refresh_token_expires_in" INTEGER, - - CONSTRAINT "accounts_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Session" ( - "id" TEXT NOT NULL, - "sessionToken" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "expires" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Session_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "users" ( - "id" TEXT NOT NULL, - "name" TEXT, - "email" TEXT, - "emailVerified" TIMESTAMP(3), - "image" TEXT, - "username" TEXT, - "role" TEXT DEFAULT 'user', - "password" TEXT, - - CONSTRAINT "users_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "verification_tokens" ( - "identifier" TEXT NOT NULL, - "token" TEXT NOT NULL, - "expires" TIMESTAMP(3) NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "accounts_provider_providerAccountId_key" ON "accounts"("provider", "providerAccountId"); - --- CreateIndex -CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); - --- CreateIndex -CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "users_username_key" ON "users"("username"); - --- CreateIndex -CREATE UNIQUE INDEX "verification_tokens_token_key" ON "verification_tokens"("token"); - --- CreateIndex -CREATE UNIQUE INDEX "verification_tokens_identifier_token_key" ON "verification_tokens"("identifier", "token"); diff --git a/client/prisma/schema.prisma b/client/prisma/schema.prisma index e0132e7f..16a684bd 100644 --- a/client/prisma/schema.prisma +++ b/client/prisma/schema.prisma @@ -1,15 +1,14 @@ generator client { provider = "prisma-client-js" - previewFeatures = ["referentialIntegrity", "fullTextSearch"] + previewFeatures = ["fullTextSearch"] } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") - referentialIntegrity = "prisma" + provider = "postgresql" + url = env("DATABASE_URL") + relationMode = "prisma" } - model SequelizeMeta { name String @id } @@ -18,7 +17,7 @@ model File { id String @id @default(cuid()) title String content String - sha String + sha String html String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -27,6 +26,7 @@ model File { postId String post Post @relation(fields: [postId], references: [id], onDelete: Cascade) + @@index([postId, userId, id]) @@map("files") } @@ -41,37 +41,35 @@ model Post { expiresAt DateTime? parentId String? description String? - author User? @relation(fields: [authorId], references: [id]) authorId String files File[] + author User? @relation(fields: [authorId], references: [id]) + @@index([authorId, id]) @@map("posts") } -// Next auth stuff, from https://next-auth.js.org/adapters/prisma - model Account { - id String @id @default(cuid()) - userId String - type String - provider String - providerAccountId String - refresh_token String? @db.Text - access_token String? @db.Text - expires_at Int? - token_type String? - scope String? - id_token String? @db.Text - session_state String? - createdAt DateTime @default(now()) @map(name: "created_at") - updatedAt DateTime @default(now()) @map(name: "updated_at") - // https://next-auth.js.org/providers/github + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? + access_token String? + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") refresh_token_expires_in Int? - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) - @@map(name: "accounts") + @@index([userId, providerAccountId], map: "accounts_provider_account_id") + @@map("accounts") } model Session { @@ -80,6 +78,9 @@ model Session { userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId, expires], map: "sessions_user_id_expires") + @@map("sessions") } model User { @@ -88,16 +89,13 @@ model User { email String? @unique emailVerified DateTime? image String? + role String? @default("user") createdAt DateTime @default(now()) + displayName String? updatedAt DateTime @updatedAt - - accounts Account[] - sessions Session[] - - // custom fields - posts Post[] - role String? @default("user") - displayName String? + posts Post[] + accounts Account[] + sessions Session[] @@map("users") }