gen prisma migration, fix up rendering html (somewhat) in RSC

This commit is contained in:
Max Leiter 2022-11-12 01:57:30 -08:00
parent 86b9172527
commit 096cf41eee
17 changed files with 218 additions and 137 deletions

View file

@ -1,5 +1,3 @@
import { TOKEN_COOKIE_NAME } from "@lib/constants"
import { getCookie } from "cookies-next"
import { memo, useEffect, useState } from "react" import { memo, useEffect, useState } from "react"
import styles from "./preview.module.css" import styles from "./preview.module.css"
@ -30,7 +28,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => {
content content
}) })
const resp = await fetch(`/api/files/get-html?${urlQuery}`, { const resp = await fetch(`/api/file/get-html?${urlQuery}`, {
method: "GET" method: "GET"
}) })

View file

@ -12,7 +12,7 @@ import FormattingIcons from "./formatting-icons"
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor" import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
import { Button, Input, Spacer, Tabs, Textarea } from "@geist-ui/core/dist" 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" // import Link from "next/link"
type Props = { type Props = {

View file

@ -14,7 +14,7 @@ import {
Tooltip, Tooltip,
Tag Tag
} from "@geist-ui/core/dist" } 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 FadeIn from "@components/fade-in"
// import Link from "next/link" // import Link from "next/link"

View file

@ -4,7 +4,7 @@ import { Spacer, Tabs, Card, Textarea, Text } from "@geist-ui/core/dist"
import Image from "next/image" import Image from "next/image"
import styles from "./home.module.css" import styles from "./home.module.css"
// TODO:components/new-post/ move these styles // 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 = ({ const Home = ({
introTitle, introTitle,
introContent, introContent,

View file

@ -9,7 +9,7 @@ export default async function Mine() {
const userId = (await getCurrentUser())?.id const userId = (await getCurrentUser())?.id
if (!userId) { if (!userId) {
redirect(authOptions.pages?.signIn || "/new") return redirect(authOptions.pages?.signIn || "/new")
} }
const posts = await getPostsByUser(userId, true) const posts = await getPostsByUser(userId, true)

View file

View file

@ -1,8 +1,6 @@
import { marked } from "marked" import { marked } from "marked"
// import Highlight, { defaultProps, Language } from "prism-react-renderer" import Highlight, { defaultProps, Language } from "prism-react-renderer"
import { renderToStaticMarkup } from "react-dom/server"
import Image from "next/image" import Image from "next/image"
import Link from "next/link"
// // image sizes. DDoS Safe? // // image sizes. DDoS Safe?
// const imageSizeLink = /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?(?:\s+=(?:[\w%]+)?x(?:[\w%]+)?)?)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/; // 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; // Lexer.rules.inline.breaks.link = imageSizeLink;
//@ts-ignore //@ts-ignore
// delete defaultProps.theme delete defaultProps.theme
// import linkStyles from '../components/link/link.module.css' // import linkStyles from '../components/link/link.module.css'
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
@ -33,20 +31,18 @@ const renderer = new marked.Renderer()
// ) // )
// } // }
// renderer.link = (href, _, text) => { renderer.link = (href, _, text) => {
// const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') const isHrefLocal = href?.startsWith('/') || href?.startsWith('#')
// if (isHrefLocal) { if (isHrefLocal) {
// return renderToStaticMarkup( return <a href={href || ''}>
// <a href={href || ''}> {text}
// {text} </a>
// </a> }
// )
// }
// // dirty hack // dirty hack
// // if text contains elements, render as html // if text contains elements, render as html
// return <a href={href || ""} target="_blank" rel="noopener noreferrer" dangerouslySetInnerHTML={{ __html: convertHtmlEntities(text) }} ></a> return <a href={href || ""} target="_blank" rel="noopener noreferrer" dangerouslySetInnerHTML={{ __html: convertHtmlEntities(text) }} ></a>
// } }
// @ts-ignore // @ts-ignore
renderer.image = function (href, _, text) { renderer.image = function (href, _, text) {
@ -71,21 +67,20 @@ renderer.listitem = (text, task, checked) => {
return <li>{text}</li> return <li>{text}</li>
} }
//@ts-ignore // //@ts-ignore
renderer.code = (code: string, language: string) => { // renderer.code = (code: string, language: string) => {
return ( // return (<pre>
<pre> // {/* {title && <code>{title} </code>} */}
{/* {title && <code>{title} </code>} */} // {/* {language && title && <code style={{}}> {language} </code>} */}
{/* {language && title && <code style={{}}> {language} </code>} */} // <Code
<Code // language={language}
language={language} // // title={title}
// title={title} // code={code}
code={code} // // highlight={highlight}
// highlight={highlight} // />
/> // </pre>
</pre> // )
) // }
}
marked.setOptions({ marked.setOptions({
gfm: true, gfm: true,
@ -136,7 +131,7 @@ const Code = ({
// https://mdxjs.com/guides/syntax-harkedighlighting#all-together // https://mdxjs.com/guides/syntax-harkedighlighting#all-together
return ( return (
<> <>
{/* <Highlight <Highlight
{...defaultProps} {...defaultProps}
code={code.trim()} code={code.trim()}
language={language as Language} language={language as Language}
@ -177,7 +172,7 @@ const Code = ({
))} ))}
</code> </code>
)} )}
</Highlight> */} </Highlight>
<> <>
<code {...props} dangerouslySetInnerHTML={{ __html: code }} /> <code {...props} dangerouslySetInnerHTML={{ __html: code }} />
</> </>

View file

@ -24,10 +24,6 @@ const nextConfig = {
}, },
async rewrites() { async rewrites() {
return [ return [
{
source: "/server-api/:path*",
destination: `${process.env.API_URL}/:path*`
},
{ {
source: "/file/raw/:id", source: "/file/raw/:id",
destination: `/api/raw/:id` destination: `/api/raw/:id`

View file

@ -4,10 +4,9 @@ import { parseQueryParam } from "@lib/server/parse-query-param"
import prisma from "@lib/server/prisma" import prisma from "@lib/server/prisma"
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
export default withMethods(["GET"], ( export default withMethods(
req: NextApiRequest, ["GET"],
res: NextApiResponse async (req: NextApiRequest, res: NextApiResponse) => {
) => {
const query = req.query const query = req.query
const fileId = parseQueryParam(query.fileId) const fileId = parseQueryParam(query.fileId)
const content = parseQueryParam(query.content) const content = parseQueryParam(query.content)
@ -44,4 +43,5 @@ export default withMethods(["GET"], (
res.end() res.end()
return return
} }
} }
)

View file

@ -9,6 +9,8 @@ const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => {
} }
}) })
console.log("file", file, "id", req.query.id)
if (!file) { if (!file) {
return res.status(404).end() return res.status(404).end()
} }

View file

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

View file

@ -1,14 +1,8 @@
// nextjs typescript api handler // nextjs typescript api handler
import { withCurrentUser } from "@lib/api-middleware/with-current-user"
import { withMethods } from "@lib/api-middleware/with-methods" import { withMethods } from "@lib/api-middleware/with-methods"
import {
NextApiHandlerWithParsedBody,
withValidation
} from "@lib/api-middleware/with-validation"
import { authOptions } from "@lib/server/auth" 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 prisma, { getPostById } from "@lib/server/prisma"
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
import { unstable_getServerSession } from "next-auth/next" import { unstable_getServerSession } from "next-auth/next"

View file

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

View file

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

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 = "postgresql"

View file

@ -9,21 +9,6 @@ datasource db {
referentialIntegrity = "prisma" 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 { model SequelizeMeta {
name String @id name String @id
@ -33,7 +18,7 @@ model File {
id String @id @default(cuid()) id String @id @default(cuid())
title String title String
content String content String
sha String @unique sha String
html String html String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt