Compare commits

...

4 commits

Author SHA1 Message Date
renovate[bot]
3afcea69eb
chore(deps): update jest monorepo to v29 2022-11-14 23:04:48 +00:00
dependabot[bot]
53059e14ad
build(deps): bump sqlite3 from 5.0.2 to 5.0.3 in /server (#128)
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.2 to 5.0.3.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.2...v5.0.3)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 00:31:23 -08:00
Max Leiter
55c5ecfe6c
Update to next 13, switch to pnpm (#127)
* switch to pnpm

* dep improvements, style fixes, next/link codemod

* server: upgrade sqlite
2022-11-08 00:28:19 -08:00
Max Leiter
9771e64f93
Update README.md 2022-11-07 23:06:27 -08:00
33 changed files with 9738 additions and 4604 deletions

View file

@ -2,7 +2,7 @@
Drift is a self-hostable Gist clone. It's also a major work-in-progress, but is completely functional.
You can try a demo at https://drift.maxleiter.com. The demo is built on master but has no database, so files and accounts can be wiped at any time.
You can try a demo at https://drift.lol. The demo is built on master but has no database, so files and accounts can be wiped at any time.
If you want to contribute, need support, or want to stay updated, you can join the IRC channel at #drift on irc.libera.chat or [reach me on twitter](https://twitter.com/Max_Leiter). If you don't have an IRC client yet, you can use a webclient [here](https://demo.thelounge.chat/#/connect?join=%23drift&nick=drift-user&realname=Drift%20User).
<hr />
@ -92,3 +92,5 @@ Drift is a major work in progress. Below is a (rough) list of completed and envi
- [ ] works enough with JavaScript disabled
- [x] documentation
- [x] customizable homepage, so the demo can exist as-is but other instances can be built from the same source. Environment variable for the file contents?
- [ ] Next.js 13 + app directory / server components
- [ ] Migrate away from `geist-ui`

4
client/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

View file

@ -1,17 +0,0 @@
import type { LinkProps } from "@geist-ui/core"
import { Link as GeistLink } from "@geist-ui/core"
import { useRouter } from "next/router"
const Link = (props: LinkProps) => {
const { basePath } = useRouter()
const propHrefWithoutLeadingSlash =
props.href && props.href.startsWith("/")
? props.href.substring(1)
: props.href
const href = basePath
? `${basePath}/${propHrefWithoutLeadingSlash}`
: props.href
return <GeistLink {...props} href={href} />
}
export default Link

View file

@ -1,4 +1,3 @@
import { Text, Spacer } from "@geist-ui/core"
import Cookies from "js-cookie"
import styles from "./admin.module.css"
import PostTable from "./post-table"
@ -23,11 +22,19 @@ export const adminFetcher = async (
const Admin = () => {
return (
<div className={styles.adminWrapper}>
<Text h2>Administration</Text>
<h2>Administration</h2>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 4
}}
>
<UserTable />
<Spacer height={1} />
<PostTable />
</div>
</div>
)
}

View file

@ -1,10 +1,12 @@
import { FormEvent, useEffect, useState } from "react"
import { Button, Input, Text, Note } from "@geist-ui/core"
import styles from "./auth.module.css"
import { useRouter } from "next/router"
import Link from "../Link"
import Link from "../link"
import Cookies from "js-cookie"
import useSignedIn from "@lib/hooks/use-signed-in"
import Input from "@components/input"
import Button from "@components/button"
import Note from "@components/note"
const NO_EMPTY_SPACE_REGEX = /^\S*$/
const ERROR_MESSAGE =
@ -90,58 +92,57 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
<form onSubmit={handleSubmit}>
<div className={styles.formGroup}>
<Input
htmlType="text"
type="text"
id="username"
value={username}
onChange={(event) => setUsername(event.target.value)}
onChange={(event) => setUsername(event.currentTarget.value)}
placeholder="Username"
required
scale={4 / 3}
/>
<Input
htmlType="password"
type="password"
id="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
onChange={(event) => setPassword(event.currentTarget.value)}
placeholder="Password"
required
scale={4 / 3}
/>
{requiresServerPassword && (
<Input
htmlType="password"
type="password"
id="server-password"
value={serverPassword}
onChange={(event) => setServerPassword(event.target.value)}
onChange={(event) =>
setServerPassword(event.currentTarget.value)
}
placeholder="Server Password"
required
scale={4 / 3}
/>
)}
<Button type="success" htmlType="submit">
<Button buttonType="primary" type="submit">
{signingIn ? "Sign In" : "Sign Up"}
</Button>
</div>
<div className={styles.formContentSpace}>
{signingIn ? (
<Text>
<p>
Don&apos;t have an account?{" "}
<Link color href="/signup">
<Link colored href="/signup">
Sign up
</Link>
</Text>
</p>
) : (
<Text>
<p>
Already have an account?{" "}
<Link color href="/signin">
<Link colored href="/signin">
Sign in
</Link>
</Text>
</p>
)}
</div>
{errorMsg && (
<Note scale={0.75} type="error">
<Note type="error">
{errorMsg}
</Note>
)}

View file

@ -1,26 +1,22 @@
.button:root {
--hover: var(--bg);
--hover-bg: var(--fg);
}
.button {
user-select: none;
cursor: pointer;
border-radius: var(--radius);
color: var(--input-fg);
font-weight: 400;
font-size: 1.1rem;
background: var(--input-bg);
border: var(--input-border);
height: 2rem;
display: flex;
align-items: center;
padding: var(--gap-quarter) var(--gap-half);
transition: background-color var(--transition), color var(--transition);
width: 100%;
height: var(--input-height);
border: 1px solid var(--border);
padding: var(--gap-half) var(--gap);
}
.button:hover,
.button:focus {
outline: none;
background: var(--input-bg-hover);
border: var(--input-border-focus);
color: var(--hover);
background: var(--hover-bg);
border: var(--);
}
.button[disabled] {
@ -38,3 +34,20 @@
background: var(--fg);
color: var(--bg);
}
.icon {
display: inline-block;
width: 1em;
height: 1em;
vertical-align: middle;
}
.iconRight {
margin-left: var(--gap-half);
}
.icon svg {
display: block;
width: 100%;
height: 100%;
}

View file

@ -6,6 +6,7 @@ type Props = React.HTMLProps<HTMLButtonElement> & {
buttonType?: "primary" | "secondary"
className?: string
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
iconRight?: React.ReactNode
}
// eslint-disable-next-line react/display-name
@ -18,6 +19,7 @@ const Button = forwardRef<HTMLButtonElement, Props>(
buttonType = "primary",
type = "button",
disabled = false,
iconRight,
...props
},
ref
@ -31,6 +33,11 @@ const Button = forwardRef<HTMLButtonElement, Props>(
{...props}
>
{children}
{iconRight && (
<span className={`${styles.icon} ${styles.iconRight}`}>
{iconRight}
</span>
)}
</button>
)
}

View file

@ -1,16 +1,29 @@
import { File } from "@lib/types"
import { Card, Link, Text } from "@geist-ui/core"
import FileIcon from "@geist-ui/icons/fileText"
import CodeIcon from "@geist-ui/icons/fileLambda"
import styles from "./file-tree.module.css"
import ShiftBy from "@components/shift-by"
import { useEffect, useState } from "react"
import { codeFileExtensions } from "@lib/constants"
import Link from "@components/link"
type Item = File & {
icon: JSX.Element
}
const Card = ({
children,
className,
...props
}: {
children: React.ReactNode
className?: string
} & React.ComponentProps<"div">) => (
<div className={styles.card} {...props}>
{children}
</div>
)
const FileTree = ({ files }: { files: File[] }) => {
const [items, setItems] = useState<Item[]>([])
useEffect(() => {
@ -34,13 +47,13 @@ const FileTree = ({ files }: { files: File[] }) => {
// a list of files with an icon and a title
return (
<div className={styles.fileTreeWrapper}>
<Card height={"100%"} className={styles.card}>
<Card className={styles.card}>
<div className={styles.cardContent}>
<Text h4>Files</Text>
<h4>Files</h4>
<ul className={styles.fileTree}>
{items.map(({ id, title, icon }) => (
<li key={id}>
<Link color={false} href={`#${title}`}>
<Link href={`#${title}`}>
<ShiftBy y={5}>
<span className={styles.fileTreeIcon}>{icon}</span>
</ShiftBy>

View file

@ -1,3 +1,5 @@
'use client';
import {
ButtonGroup,
Button,
@ -168,8 +170,7 @@ const Header = () => {
)
} else if (tab.href) {
return (
<Link key={tab.value} href={tab.href}>
<a className={styles.tab}>
(<Link key={tab.value} href={tab.href} className={styles.tab}>
<Button
className={activeStyle}
auto={isMobile ? false : true}
@ -178,9 +179,9 @@ const Header = () => {
>
{tab.name ? tab.name : undefined}
</Button>
</a>
</Link>
)
</Link>)
);
}
},
[isMobile, onTabChange, router.pathname]

View file

@ -20,8 +20,8 @@ const Home = ({
<ShiftBy y={-2}>
<Image
src={"/assets/logo-optimized.svg"}
width={"48px"}
height={"48px"}
width={48}
height={48}
alt=""
/>
</ShiftBy>

View file

@ -23,7 +23,7 @@
}
.input::placeholder {
font-size: 1.5rem;
font-size: 1rem;
}
.input:focus {

View file

@ -0,0 +1,26 @@
import { useRouter } from "next/router"
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 className = colored ? `${styles.link} ${styles.color}` : styles.link
return (
<NextLink {...props} href={url} className={className}>
{children}
</NextLink>
)
}
export default Link

View file

@ -0,0 +1,12 @@
.link {
text-decoration: none;
color: var(--fg);
}
.color {
color: var(--link);
}
.color:hover {
text-decoration: underline;
}

View file

@ -169,10 +169,7 @@ const Post = ({
setSubmitting(false)
}
const submitPassword = useCallback(
(password: string) => onSubmit("protected", password),
[onSubmit]
)
const submitPassword = (password: string) => onSubmit("protected", password)
const onChangeExpiration = useCallback((date: Date) => setExpiresAt(date), [])
@ -199,24 +196,17 @@ const Post = ({
[setDocs]
)
const updateDocContent = useCallback(
(i: number) => (content: string) => {
const updateDocContent = (i: number) => (content: string) => {
setDocs((docs) =>
docs.map((doc, index) => (i === index ? { ...doc, content } : doc))
)
},
[setDocs]
)
}
const removeDoc = useCallback(
(i: number) => () => {
const removeDoc = (i: number) => () => {
setDocs((docs) => docs.filter((_, index) => i !== index))
},
[setDocs]
)
}
const uploadDocs = useCallback(
(files: DocumentType[]) => {
const uploadDocs = (files: DocumentType[]) => {
// if no title is set and the only document is empty,
const isFirstDocEmpty =
docs.length <= 1 && (docs.length ? docs[0].title === "" : true)
@ -231,9 +221,7 @@ const Post = ({
if (isFirstDocEmpty) setDocs(files)
else setDocs((docs) => [...docs, ...files])
},
[docs, title]
)
}
// pasted files
// const files = e.clipboardData.files as File[]
@ -340,15 +328,15 @@ const Post = ({
/>
}
<ButtonDropdown loading={isSubmitting} type="success">
<ButtonDropdown.Item onClick={() => onSubmit("unlisted")}>
Create Unlisted
</ButtonDropdown.Item>
<ButtonDropdown.Item main onClick={() => onSubmit("private")}>
Create Private
</ButtonDropdown.Item>
<ButtonDropdown.Item onClick={() => onSubmit("public")}>
Create Public
</ButtonDropdown.Item>
<ButtonDropdown.Item onClick={() => onSubmit("unlisted")}>
Create Unlisted
</ButtonDropdown.Item>
<ButtonDropdown.Item onClick={() => onSubmit("protected")}>
Create with Password
</ButtonDropdown.Item>

View file

@ -0,0 +1,17 @@
import styles from "./note.module.css"
const Note = ({
type = "info",
children,
...props
}: {
type: "info" | "warning" | "error"
children: React.ReactNode
} & React.ComponentProps<"div">) => (
<div className={`${styles.note} ${styles[type]}`} {...props}>
<strong className={styles.type}>{type}:</strong>
{children}
</div>
)
export default Note

View file

@ -0,0 +1,27 @@
.note {
font-size: 0.8em;
color: var(--fg);
margin: 0;
padding: var(--gap);
margin-top: 0.5em;
margin-bottom: 0.5em;
border-radius: var(--radius);
}
.info {
background: var(--gray);
}
.warning {
background: #f33;
}
.error {
background: red;
}
.type {
color: var(--fg);
margin-right: 0.5em;
text-transform: capitalize;
}

View file

@ -1,6 +1,4 @@
import { Button, Input, Select, Text } from "@geist-ui/core"
import NextLink from "next/link"
import Link from "../Link"
import { Button, Input, Text } from "@geist-ui/core"
import styles from "./post-list.module.css"
import ListItemSkeleton from "./list-item-skeleton"
@ -9,6 +7,7 @@ import { Post } from "@lib/types"
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"
import Cookies from "js-cookie"
import useDebounce from "@lib/hooks/use-debounce"
import Link from "@components/link"
type Props = {
initialPosts: Post[]
@ -136,9 +135,9 @@ const PostList = ({ morePosts, initialPosts, error }: Props) => {
{posts?.length === 0 && !error && (
<Text type="secondary">
No posts found. Create one{" "}
<NextLink passHref={true} href="/new">
<Link color>here</Link>
</NextLink>
<Link colored href="/new">
here
</Link>
.
</Text>
)}

View file

@ -1,14 +1,6 @@
import NextLink from "next/link"
import VisibilityBadge from "../badges/visibility-badge"
import {
Link,
Text,
Card,
Tooltip,
Divider,
Badge,
Button
} from "@geist-ui/core"
import { Text, Card, Tooltip, Divider, Badge, Button } from "@geist-ui/core"
import { File, Post } from "@lib/types"
import FadeIn from "@components/fade-in"
import Trash from "@geist-ui/icons/trash"
@ -18,6 +10,7 @@ import Edit from "@geist-ui/icons/edit"
import { useRouter } from "next/router"
import Parent from "@geist-ui/icons/arrowUpCircle"
import styles from "./list-item.module.css"
import Link from "@components/link"
// TODO: isOwner should default to false so this can be used generically
const ListItem = ({
@ -45,15 +38,14 @@ const ListItem = ({
<Card style={{ overflowY: "scroll" }}>
<Card.Body>
<Text h3 className={styles.title}>
<NextLink
passHref={true}
<Link
colored
style={{ marginRight: "var(--gap)" }}
href={`/post/[id]`}
as={`/post/${post.id}`}
>
<Link color marginRight={"var(--gap)"}>
{post.title}
</Link>
</NextLink>
{isOwner && (
<span className={styles.buttons}>
{post.parent && (
@ -97,7 +89,7 @@ const ListItem = ({
{post.files?.map((file: File) => {
return (
<div key={file.id}>
<Link color href={`/post/${post.id}#${file.title}`}>
<Link colored href={`/post/${post.id}#${file.title}`}>
{file.title || "Untitled file"}
</Link>
</div>

View file

@ -3,16 +3,15 @@ import styles from "./document.module.css"
import Download from "@geist-ui/icons/download"
import ExternalLink from "@geist-ui/icons/externalLink"
import Skeleton from "react-loading-skeleton"
import Link from 'next/link';
import {
Button,
Text,
ButtonGroup,
Spacer,
Tabs,
Textarea,
Tooltip,
Link,
Tag
} from "@geist-ui/core"
import HtmlPreview from "@components/preview"
@ -32,7 +31,7 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
<div className={styles.actionWrapper}>
<ButtonGroup className={styles.actions}>
<Tooltip hideArrow text="Download">
<a
<Link
href={`${rawLink}?download=true`}
target="_blank"
rel="noopener noreferrer"
@ -44,10 +43,10 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
auto
aria-label="Download"
/>
</a>
</Link>
</Tooltip>
<Tooltip hideArrow text="Open raw in new tab">
<a href={rawLink} target="_blank" rel="noopener noreferrer">
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
<Button
scale={2 / 3}
px={0.6}
@ -55,7 +54,7 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
auto
aria-label="Open raw file in new tab"
/>
</a>
</Link>
</Tooltip>
</ButtonGroup>
</div>

View file

@ -16,7 +16,7 @@ export default function generateUUID() {
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))
).toString(16)
}
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback)
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback);
}
}
let timestamp = new Date().getTime()
@ -35,5 +35,5 @@ export default function generateUUID() {
perforNow = Math.floor(perforNow / 16)
}
return (c === "x" ? random : (random & 0x3) | 0x8).toString(16)
})
});
}

View file

@ -1,14 +1,14 @@
import { NextFetchEvent, NextRequest, NextResponse } from "next/server"
import { NextFetchEvent, NextResponse } from "next/server"
import type { NextRequest } from "next/server"
const PUBLIC_FILE = /\.(.*)$/
export function middleware(req: NextRequest, event: NextFetchEvent) {
const pathname = req.nextUrl.pathname
const signedIn = req.cookies["drift-token"]
const signedIn = req.cookies.get("drift-token")
const getURL = (pageName: string) => new URL(`/${pageName}`, req.url).href
const isPageRequest =
!PUBLIC_FILE.test(pathname) &&
!pathname.startsWith("/api") &&
// header added when next/link pre-fetches a route
!req.headers.get("x-middleware-preflight")
@ -17,8 +17,8 @@ export function middleware(req: NextRequest, event: NextFetchEvent) {
// If you're not signed in we redirect to the home page
if (signedIn) {
const resp = NextResponse.redirect(getURL(""))
resp.clearCookie("drift-token")
resp.clearCookie("drift-userid")
resp.cookies.delete("drift-token")
resp.cookies.delete("drift-userid")
const signoutPromise = new Promise((resolve) => {
fetch(`${process.env.API_URL}/auth/signout`, {
method: "POST",
@ -61,3 +61,17 @@ export function middleware(req: NextRequest, event: NextFetchEvent) {
return NextResponse.next()
}
export const config = {
match: [
"/signout",
"/",
"/signin",
"/signup",
"/new",
"/protected/:path*",
"/private/:path*"
]
}

View file

@ -7,8 +7,9 @@ dotenv.config()
const nextConfig = {
reactStrictMode: true,
experimental: {
outputStandalone: true,
esmExternals: true
// outputStandalone: true,
esmExternals: true,
// appDir: true
},
webpack: (config, { dev, isServer }) => {
if (!dev && !isServer) {

View file

@ -11,7 +11,7 @@
"find:unused": "next-unused"
},
"dependencies": {
"@geist-ui/core": "2.3.8",
"@geist-ui/core": "^2.3.8",
"@geist-ui/icons": "1.0.2",
"@types/cookie": "0.5.1",
"@types/js-cookie": "3.0.2",
@ -19,13 +19,13 @@
"cookie": "0.5.0",
"dotenv": "16.0.0",
"js-cookie": "3.0.1",
"next": "12.1.6",
"next-themes": "0.2.0",
"next": "13.0.2",
"next-themes": "0.2.1",
"rc-table": "7.24.1",
"react": "18.1.0",
"react-datepicker": "4.7.0",
"react-dom": "18.1.0",
"react-dropzone": "12.1.0",
"react": "18.2.0",
"react-datepicker": "4.8.0",
"react-dom": "18.2.0",
"react-dropzone": "14.2.3",
"react-loading-skeleton": "3.1.0",
"swr": "1.3.0",
"textarea-markdown-editor": "0.1.13"
@ -37,13 +37,16 @@
"@types/react-datepicker": "4.4.1",
"@types/react-dom": "18.0.3",
"cross-env": "7.0.3",
"eslint": "8.15.0",
"eslint-config-next": "12.1.6",
"eslint": "8.27.0",
"eslint-config-next": "13.0.2",
"next-unused": "0.0.6",
"prettier": "2.6.2",
"typescript": "4.6.4",
"typescript-plugin-css-modules": "3.4.0"
},
"optionalDependencies": {
"sharp": "^0.31.2"
},
"next-unused": {
"alias": {
"@components": "components/",
@ -54,5 +57,8 @@
"components",
"lib"
]
},
"overrides": {
"next": "13.0.2"
}
}

View file

@ -49,11 +49,9 @@ function MyApp({ Component, pageProps }: AppProps) {
<meta name="theme-color" content="#ffffff" />
<title>Drift</title>
</Head>
<React.StrictMode>
<ThemeProvider defaultTheme="system" disableTransitionOnChange>
<App Component={Component} pageProps={pageProps} />
</ThemeProvider>
</React.StrictMode>
</div>
)
}

View file

@ -1,6 +1,5 @@
import styles from "@styles/Home.module.css"
import Header from "@components/header"
import { Page } from "@geist-ui/core"
import { useEffect } from "react"
import Admin from "@components/admin"

View file

@ -1,4 +1,3 @@
import Header from "@components/header"
import { Note, Page, Text } from "@geist-ui/core"
import styles from "@styles/Home.module.css"

3740
client/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

View file

@ -48,8 +48,9 @@
--header-bg: rgba(19, 20, 21, 0.45);
--gray-alpha: rgba(255, 255, 255, 0.5);
--selection: rgba(255, 255, 255, 0.99);
--border: var(--lighter-gray);
--warning: rgb(27, 134, 23);
--link: #3291ff;
}
[data-theme="light"] {

View file

@ -1,8 +1,19 @@
{
"compilerOptions": {
"plugins": [{ "name": "typescript-plugin-css-modules" }],
"plugins": [
{
"name": "typescript-plugin-css-modules"
},
{
"name": "next"
}
],
"target": "es2020",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -27,11 +38,24 @@
"incremental": true,
"baseUrl": ".",
"paths": {
"@components/*": ["components/*"],
"@lib/*": ["lib/*"],
"@styles/*": ["styles/*"]
"@components/*": [
"components/*"
],
"@lib/*": [
"lib/*"
],
"@styles/*": [
"styles/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,7 @@
"reflect-metadata": "^0.1.10",
"sequelize": "^6.17.0",
"sequelize-typescript": "^2.1.3",
"sqlite3": "https://github.com/mapbox/node-sqlite3#918052b538b0effe6c4a44c74a16b2749c08a0d2",
"sqlite3": "^5.0.3",
"strong-error-handler": "^4.0.0",
"umzug": "^3.1.0"
},
@ -43,18 +43,19 @@
"@types/cors": "2.8.12",
"@types/express": "4.17.13",
"@types/express-jwt": "6.0.4",
"@types/jest": "27.5.0",
"@types/jest": "29.2.3",
"@types/jsonwebtoken": "8.5.8",
"@types/marked": "4.0.3",
"@types/node": "17.0.21",
"@types/node-fetch": "2.6.1",
"@types/react-dom": "17.0.16",
"@types/supertest": "2.0.12",
"@types/validator": "^13.7.10",
"cross-env": "7.0.3",
"jest": "27.5.1",
"jest": "29.3.1",
"prettier": "2.6.2",
"supertest": "6.2.3",
"ts-jest": "27.1.4",
"ts-jest": "29.0.3",
"ts-node": "10.7.0",
"tsc-alias": "1.6.7",
"tsconfig-paths": "3.14.1",

4588
server/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff