diff --git a/client/app/(posts)/new/components/edit-document-list/edit-document/preview/index.tsx b/client/app/(posts)/components/preview/index.tsx similarity index 88% rename from client/app/(posts)/new/components/edit-document-list/edit-document/preview/index.tsx rename to client/app/(posts)/components/preview/index.tsx index 3ab0fb3a..1ae035b8 100644 --- a/client/app/(posts)/new/components/edit-document-list/edit-document/preview/index.tsx +++ b/client/app/(posts)/components/preview/index.tsx @@ -1,5 +1,3 @@ -import { TOKEN_COOKIE_NAME } from "@lib/constants" -import { getCookie } from "cookies-next" import { memo, useEffect, useState } from "react" import styles from "./preview.module.css" @@ -30,7 +28,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { content }) - const resp = await fetch(`/api/files/get-html?${urlQuery}`, { + const resp = await fetch(`/api/file/get-html?${urlQuery}`, { method: "GET" }) diff --git a/client/app/(posts)/new/components/edit-document-list/edit-document/preview/preview.module.css b/client/app/(posts)/components/preview/preview.module.css similarity index 100% rename from client/app/(posts)/new/components/edit-document-list/edit-document/preview/preview.module.css rename to client/app/(posts)/components/preview/preview.module.css diff --git a/client/app/(posts)/new/components/edit-document-list/edit-document/index.tsx b/client/app/(posts)/new/components/edit-document-list/edit-document/index.tsx index ea57157b..962ef182 100644 --- a/client/app/(posts)/new/components/edit-document-list/edit-document/index.tsx +++ b/client/app/(posts)/new/components/edit-document-list/edit-document/index.tsx @@ -12,7 +12,7 @@ import FormattingIcons from "./formatting-icons" import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor" import { Button, Input, Spacer, Tabs, Textarea } from "@geist-ui/core/dist" -import Preview from "./preview" +import Preview from "../../../../components/preview" // import Link from "next/link" type Props = { 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 08a018ab..bf0036f0 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 @@ -14,7 +14,7 @@ import { Tooltip, Tag } from "@geist-ui/core/dist" -import HtmlPreview from "app/(posts)/new/components/edit-document-list/edit-document/preview" +import HtmlPreview from "app/(posts)/components/preview" import FadeIn from "@components/fade-in" // import Link from "next/link" diff --git a/client/app/components/home.tsx b/client/app/components/home.tsx index cbc69933..607c9b9a 100644 --- a/client/app/components/home.tsx +++ b/client/app/components/home.tsx @@ -4,7 +4,7 @@ import { Spacer, Tabs, Card, Textarea, Text } from "@geist-ui/core/dist" import Image from "next/image" import styles from "./home.module.css" // TODO:components/new-post/ move these styles -import markdownStyles from "app/(posts)/new/components/edit-document-list/edit-document/preview/preview.module.css"; +import markdownStyles from "app/(posts)/components/preview/preview.module.css"; const Home = ({ introTitle, introContent, diff --git a/client/app/mine/page.tsx b/client/app/mine/page.tsx index f35be304..52b3c511 100644 --- a/client/app/mine/page.tsx +++ b/client/app/mine/page.tsx @@ -9,7 +9,7 @@ export default async function Mine() { const userId = (await getCurrentUser())?.id if (!userId) { - redirect(authOptions.pages?.signIn || "/new") + return redirect(authOptions.pages?.signIn || "/new") } const posts = await getPostsByUser(userId, true) diff --git a/client/lib/code-wrapper.tsx b/client/lib/code-wrapper.tsx new file mode 100644 index 00000000..e69de29b diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index d03b9073..20b4127f 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -1,8 +1,6 @@ import { marked } from "marked" -// import Highlight, { defaultProps, Language } from "prism-react-renderer" -import { renderToStaticMarkup } from "react-dom/server" +import Highlight, { defaultProps, Language } from "prism-react-renderer" 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*\)/; @@ -14,7 +12,7 @@ import Link from "next/link" // Lexer.rules.inline.breaks.link = imageSizeLink; //@ts-ignore -// delete defaultProps.theme +delete defaultProps.theme // import linkStyles from '../components/link/link.module.css' const renderer = new marked.Renderer() @@ -33,20 +31,18 @@ const renderer = new marked.Renderer() // ) // } -// renderer.link = (href, _, text) => { -// const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') -// if (isHrefLocal) { -// return renderToStaticMarkup( -// -// {text} -// -// ) -// } +renderer.link = (href, _, text) => { + const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') + if (isHrefLocal) { + return + {text} + + } -// // dirty hack -// // if text contains elements, render as html -// return -// } + // dirty hack + // if text contains elements, render as html + return +} // @ts-ignore renderer.image = function (href, _, text) { @@ -71,21 +67,20 @@ renderer.listitem = (text, task, checked) => { return
  • {text}
  • } -//@ts-ignore -renderer.code = (code: string, language: string) => { - return ( -
    -			{/* {title && {title} } */}
    -			{/* {language && title &&  {language} } */}
    -			
    -		
    - ) -} +// //@ts-ignore +// renderer.code = (code: string, language: string) => { +// return (
    +// 			{/* {title && {title} } */}
    +// 			{/* {language && title &&  {language} } */}
    +// 			
    +// 		
    +// ) +// } marked.setOptions({ gfm: true, @@ -136,7 +131,7 @@ const Code = ({ // https://mdxjs.com/guides/syntax-harkedighlighting#all-together return ( <> - {/* )} - */} + <> diff --git a/client/next.config.mjs b/client/next.config.mjs index b4fc3c6d..6d039b45 100644 --- a/client/next.config.mjs +++ b/client/next.config.mjs @@ -24,10 +24,6 @@ const nextConfig = { }, async rewrites() { return [ - { - source: "/server-api/:path*", - destination: `${process.env.API_URL}/:path*` - }, { source: "/file/raw/:id", destination: `/api/raw/:id` diff --git a/client/pages/api/file/get-html.ts b/client/pages/api/file/get-html.ts index cfadb1a6..6749c0f6 100644 --- a/client/pages/api/file/get-html.ts +++ b/client/pages/api/file/get-html.ts @@ -4,44 +4,44 @@ import { parseQueryParam } from "@lib/server/parse-query-param" import prisma from "@lib/server/prisma" import { NextApiRequest, NextApiResponse } from "next" -export default withMethods(["GET"], ( - req: NextApiRequest, - res: NextApiResponse -) => { - const query = req.query - const fileId = parseQueryParam(query.fileId) - const content = parseQueryParam(query.content) - const title = parseQueryParam(query.title) +export default withMethods( + ["GET"], + async (req: NextApiRequest, res: NextApiResponse) => { + const query = req.query + const fileId = parseQueryParam(query.fileId) + const content = parseQueryParam(query.content) + const title = parseQueryParam(query.title) - if (fileId && (content || title)) { - return res.status(400).json({ error: "Too many arguments" }) - } + if (fileId && (content || title)) { + return res.status(400).json({ error: "Too many arguments" }) + } - if (fileId) { - const file = await prisma.file.findUnique({ - where: { - id: fileId + if (fileId) { + const file = await prisma.file.findUnique({ + where: { + id: fileId + } + }) + + if (!file) { + return res.status(404).json({ error: "File not found" }) } - }) - 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 } - - 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 } -} +) diff --git a/client/pages/api/file/html/[id].ts b/client/pages/api/file/html/[id].ts index 8aad9f7b..a59caaad 100644 --- a/client/pages/api/file/html/[id].ts +++ b/client/pages/api/file/html/[id].ts @@ -9,6 +9,8 @@ const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { } }) + console.log("file", file, "id", req.query.id) + if (!file) { return res.status(404).end() } diff --git a/client/pages/api/file/raw/[id].ts b/client/pages/api/file/raw/[id].ts new file mode 100644 index 00000000..4e6a37e1 --- /dev/null +++ b/client/pages/api/file/raw/[id].ts @@ -0,0 +1,33 @@ +import { NextApiRequest, NextApiResponse } from "next" +import prisma from "lib/server/prisma" +import { parseQueryParam } from "@lib/server/parse-query-param" + +const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { + const { id, download } = req.query + + const file = await prisma.file.findUnique({ + where: { + id: parseQueryParam(id) + } + }) + + if (!file) { + return res.status(404).json({ error: "File not found" }) + } + + res.setHeader("Content-Type", "text/plain; charset=utf-8") + res.setHeader("Cache-Control", "s-maxage=86400") + + const { title, content } = file + + if (download) { + res.setHeader("Content-Disposition", `attachment; filename="${title}"`) + } else { + res.setHeader("Content-Disposition", `inline; filename="${title}"`) + } + + res.status(200).write(content, "utf-8") + res.end() +} + +export default getRawFile diff --git a/client/pages/api/post/index.ts b/client/pages/api/post/index.ts index e105f974..97c50c2f 100644 --- a/client/pages/api/post/index.ts +++ b/client/pages/api/post/index.ts @@ -1,14 +1,8 @@ // nextjs typescript api handler -import { withCurrentUser } from "@lib/api-middleware/with-current-user" import { withMethods } from "@lib/api-middleware/with-methods" -import { - NextApiHandlerWithParsedBody, - withValidation -} from "@lib/api-middleware/with-validation" + import { authOptions } from "@lib/server/auth" -import { CreatePostSchema } from "@lib/validations/post" -import { Post } from "@prisma/client" import prisma, { getPostById } from "@lib/server/prisma" import { NextApiRequest, NextApiResponse } from "next" import { unstable_getServerSession } from "next-auth/next" diff --git a/client/pages/api/raw/[id].ts b/client/pages/api/raw/[id].ts deleted file mode 100644 index 401e7803..00000000 --- a/client/pages/api/raw/[id].ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" - -const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { - const { id, download } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { - headers: { - Accept: "text/plain", - "x-secret-key": process.env.SECRET_KEY || "", - Authorization: `Bearer ${req.cookies["drift-token"]}` - } - }) - - res.setHeader("Content-Type", "text/plain; charset=utf-8") - res.setHeader("Cache-Control", "s-maxage=86400") - if (file.ok) { - const json = await file.json() - const data = json - const { title, content } = data - // serve the file raw as plain text - - if (download) { - res.setHeader("Content-Disposition", `attachment; filename="${title}"`) - } else { - res.setHeader("Content-Disposition", `inline; filename="${title}"`) - } - - res.status(200).write(content, "utf-8") - res.end() - } else { - res.status(404).send("File not found") - } -} - -export default getRawFile diff --git a/client/prisma/migrations/20221112095706_init/migration.sql b/client/prisma/migrations/20221112095706_init/migration.sql new file mode 100644 index 00000000..9172c0b7 --- /dev/null +++ b/client/prisma/migrations/20221112095706_init/migration.sql @@ -0,0 +1,109 @@ +-- 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/migrations/migration_lock.toml b/client/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/client/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/client/prisma/schema.prisma b/client/prisma/schema.prisma index c018046f..07dc4007 100644 --- a/client/prisma/schema.prisma +++ b/client/prisma/schema.prisma @@ -9,21 +9,6 @@ datasource db { referentialIntegrity = "prisma" } -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 DriftUser[] @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@id([id, token]) - // make id and token keys - @@unique([id, token]) -} model SequelizeMeta { name String @id @@ -33,7 +18,7 @@ model File { id String @id @default(cuid()) title String content String - sha String @unique + sha String html String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt