Use kv datastore for auth (#272)
This commit is contained in:
parent
d741351e26
commit
b6cab643ef
10 changed files with 143 additions and 173 deletions
|
|
@ -37,7 +37,7 @@ This template ships with OpenAI `gpt-3.5-turbo` as the default. However, thanks
|
||||||
|
|
||||||
You can deploy your own version of the Next.js AI Chatbot to Vercel with one click:
|
You can deploy your own version of the Next.js AI Chatbot to Vercel with one click:
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"},{"type":"postgres"}])
|
[](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"}])
|
||||||
|
|
||||||
## Creating a KV Database Instance
|
## Creating a KV Database Instance
|
||||||
|
|
||||||
|
|
@ -57,7 +57,6 @@ You will need to use the environment variables [defined in `.env.example`](.env.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm seed
|
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,26 @@
|
||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import { signIn } from '@/auth'
|
import { signIn } from '@/auth'
|
||||||
import { AuthResult } from '@/lib/types'
|
import { User } from '@/lib/types'
|
||||||
import { AuthError } from 'next-auth'
|
import { AuthError } from 'next-auth'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { kv } from '@vercel/kv'
|
||||||
|
import { ResultCode } from '@/lib/utils'
|
||||||
|
|
||||||
|
export async function getUser(email: string) {
|
||||||
|
const user = await kv.hgetall<User>(`user:${email}`)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Result {
|
||||||
|
type: string
|
||||||
|
resultCode: ResultCode
|
||||||
|
}
|
||||||
|
|
||||||
export async function authenticate(
|
export async function authenticate(
|
||||||
_prevState: AuthResult | undefined,
|
_prevState: Result | undefined,
|
||||||
formData: FormData
|
formData: FormData
|
||||||
) {
|
): Promise<Result | undefined> {
|
||||||
try {
|
try {
|
||||||
const email = formData.get('email')
|
const email = formData.get('email')
|
||||||
const password = formData.get('password')
|
const password = formData.get('password')
|
||||||
|
|
@ -27,24 +39,33 @@ export async function authenticate(
|
||||||
await signIn('credentials', {
|
await signIn('credentials', {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
redirectTo: '/'
|
redirect: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
resultCode: ResultCode.UserLoggedIn
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return { type: 'error', message: 'Invalid credentials!' }
|
return {
|
||||||
|
type: 'error',
|
||||||
|
resultCode: ResultCode.InvalidCredentials
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AuthError) {
|
if (error instanceof AuthError) {
|
||||||
switch (error.type) {
|
switch (error.type) {
|
||||||
case 'CredentialsSignin':
|
case 'CredentialsSignin':
|
||||||
return { type: 'error', message: 'Invalid credentials!' }
|
return {
|
||||||
|
type: 'error',
|
||||||
|
resultCode: ResultCode.InvalidCredentials
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Something went wrong, please try again!'
|
resultCode: ResultCode.UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,50 @@
|
||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import { signIn } from '@/auth'
|
import { signIn } from '@/auth'
|
||||||
import { db } from '@vercel/postgres'
|
import { ResultCode, getStringFromBuffer } from '@/lib/utils'
|
||||||
import { getStringFromBuffer } from '@/lib/utils'
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { AuthResult } from '@/lib/types'
|
import { kv } from '@vercel/kv'
|
||||||
|
import { getUser } from '../login/actions'
|
||||||
|
import { AuthError } from 'next-auth'
|
||||||
|
|
||||||
|
export async function createUser(
|
||||||
|
email: string,
|
||||||
|
hashedPassword: string,
|
||||||
|
salt: string
|
||||||
|
) {
|
||||||
|
const existingUser = await getUser(email)
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
resultCode: ResultCode.UserAlreadyExists
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const user = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
email,
|
||||||
|
password: hashedPassword,
|
||||||
|
salt
|
||||||
|
}
|
||||||
|
|
||||||
|
await kv.hmset(`user:${email}`, user)
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
resultCode: ResultCode.UserCreated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Result {
|
||||||
|
type: string
|
||||||
|
resultCode: ResultCode
|
||||||
|
}
|
||||||
|
|
||||||
export async function signup(
|
export async function signup(
|
||||||
_prevState: AuthResult | undefined,
|
_prevState: Result | undefined,
|
||||||
formData: FormData
|
formData: FormData
|
||||||
) {
|
): Promise<Result | undefined> {
|
||||||
const email = formData.get('email') as string
|
const email = formData.get('email') as string
|
||||||
const password = formData.get('password') as string
|
const password = formData.get('password') as string
|
||||||
|
|
||||||
|
|
@ -34,42 +69,43 @@ export async function signup(
|
||||||
)
|
)
|
||||||
const hashedPassword = getStringFromBuffer(hashedPasswordBuffer)
|
const hashedPassword = getStringFromBuffer(hashedPasswordBuffer)
|
||||||
|
|
||||||
const client = await db.connect()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.sql`
|
const result = await createUser(email, hashedPassword, salt)
|
||||||
INSERT INTO users (email, password, salt)
|
|
||||||
VALUES (${email}, ${hashedPassword}, ${salt})
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
`
|
|
||||||
|
|
||||||
await signIn('credentials', {
|
if (result.resultCode === ResultCode.UserCreated) {
|
||||||
email,
|
await signIn('credentials', {
|
||||||
password,
|
email,
|
||||||
redirect: false
|
password,
|
||||||
})
|
redirect: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return { type: 'success', message: 'Account created!' }
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { message } = error as Error
|
if (error instanceof AuthError) {
|
||||||
|
switch (error.type) {
|
||||||
if (
|
case 'CredentialsSignin':
|
||||||
message.startsWith('duplicate key value violates unique constraint')
|
return {
|
||||||
) {
|
type: 'error',
|
||||||
return { type: 'error', message: 'User already exists! Please log in.' }
|
resultCode: ResultCode.InvalidCredentials
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
resultCode: ResultCode.UnknownError
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Something went wrong! Please try again.'
|
resultCode: ResultCode.UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
client.release()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Invalid entries, please try again!'
|
resultCode: ResultCode.InvalidCredentials
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
auth.ts
19
auth.ts
|
|
@ -2,25 +2,8 @@ import NextAuth from 'next-auth'
|
||||||
import Credentials from 'next-auth/providers/credentials'
|
import Credentials from 'next-auth/providers/credentials'
|
||||||
import { authConfig } from './auth.config'
|
import { authConfig } from './auth.config'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { sql } from '@vercel/postgres'
|
|
||||||
import { getStringFromBuffer } from './lib/utils'
|
import { getStringFromBuffer } from './lib/utils'
|
||||||
|
import { getUser } from './app/login/actions'
|
||||||
interface User {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
password: string
|
|
||||||
salt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getUser(email: string): Promise<User | undefined> {
|
|
||||||
try {
|
|
||||||
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`
|
|
||||||
return user.rows[0]
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed to fetch user.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const { auth, signIn, signOut } = NextAuth({
|
export const { auth, signIn, signOut } = NextAuth({
|
||||||
...authConfig,
|
...authConfig,
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,30 @@ import Link from 'next/link'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { IconSpinner } from './ui/icons'
|
import { IconSpinner } from './ui/icons'
|
||||||
|
import { getMessageFromCode } from '@/lib/utils'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
export default function LoginForm() {
|
export default function LoginForm() {
|
||||||
|
const router = useRouter()
|
||||||
const [result, dispatch] = useFormState(authenticate, undefined)
|
const [result, dispatch] = useFormState(authenticate, undefined)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.type === 'error') {
|
if (result.type === 'error') {
|
||||||
toast.error(result.message)
|
toast.error(getMessageFromCode(result.resultCode))
|
||||||
} else {
|
} else {
|
||||||
toast.success(result.message)
|
toast.success(getMessageFromCode(result.resultCode))
|
||||||
|
router.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [result])
|
}, [result, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
action={dispatch}
|
action={dispatch}
|
||||||
className="flex flex-col items-center gap-4 space-y-3"
|
className="flex flex-col items-center gap-4 space-y-3"
|
||||||
>
|
>
|
||||||
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md dark:bg-zinc-950 md:w-96">
|
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md md:w-96 dark:bg-zinc-950">
|
||||||
<h1 className="mb-3 text-2xl font-bold">Please log in to continue.</h1>
|
<h1 className="mb-3 text-2xl font-bold">Please log in to continue.</h1>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -84,7 +88,7 @@ function LoginButton() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="flex flex-row justify-center items-center my-4 h-10 w-full rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||||
aria-disabled={pending}
|
aria-disabled={pending}
|
||||||
>
|
>
|
||||||
{pending ? <IconSpinner /> : 'Log in'}
|
{pending ? <IconSpinner /> : 'Log in'}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import Link from 'next/link'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { IconSpinner } from './ui/icons'
|
import { IconSpinner } from './ui/icons'
|
||||||
|
import { getMessageFromCode } from '@/lib/utils'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
export default function SignupForm() {
|
export default function SignupForm() {
|
||||||
|
|
@ -15,10 +16,10 @@ export default function SignupForm() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.type === 'error') {
|
if (result.type === 'error') {
|
||||||
toast.error(result.message)
|
toast.error(getMessageFromCode(result.resultCode))
|
||||||
} else {
|
} else {
|
||||||
|
toast.success(getMessageFromCode(result.resultCode))
|
||||||
router.refresh()
|
router.refresh()
|
||||||
toast.success(result.message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [result, router])
|
}, [result, router])
|
||||||
|
|
@ -28,7 +29,7 @@ export default function SignupForm() {
|
||||||
action={dispatch}
|
action={dispatch}
|
||||||
className="flex flex-col items-center gap-4 space-y-3"
|
className="flex flex-col items-center gap-4 space-y-3"
|
||||||
>
|
>
|
||||||
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md dark:bg-zinc-950 md:w-96">
|
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md md:w-96 dark:bg-zinc-950">
|
||||||
<h1 className="mb-3 text-2xl font-bold">Sign up for an account!</h1>
|
<h1 className="mb-3 text-2xl font-bold">Sign up for an account!</h1>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -85,7 +86,7 @@ function LoginButton() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="flex flex-row justify-center items-center my-4 h-10 w-full rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||||
aria-disabled={pending}
|
aria-disabled={pending}
|
||||||
>
|
>
|
||||||
{pending ? <IconSpinner /> : 'Create account'}
|
{pending ? <IconSpinner /> : 'Create account'}
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,10 @@ export interface AuthResult {
|
||||||
type: string
|
type: string
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface User extends Record<string, any> {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
salt: string
|
||||||
|
}
|
||||||
|
|
|
||||||
26
lib/utils.ts
26
lib/utils.ts
|
|
@ -61,3 +61,29 @@ export const getStringFromBuffer = (buffer: ArrayBuffer) =>
|
||||||
Array.from(new Uint8Array(buffer))
|
Array.from(new Uint8Array(buffer))
|
||||||
.map(b => b.toString(16).padStart(2, '0'))
|
.map(b => b.toString(16).padStart(2, '0'))
|
||||||
.join('')
|
.join('')
|
||||||
|
|
||||||
|
export enum ResultCode {
|
||||||
|
InvalidCredentials = 'INVALID_CREDENTIALS',
|
||||||
|
InvalidSubmission = 'INVALID_SUBMISSION',
|
||||||
|
UserAlreadyExists = 'USER_ALREADY_EXISTS',
|
||||||
|
UnknownError = 'UNKNOWN_ERROR',
|
||||||
|
UserCreated = 'USER_CREATED',
|
||||||
|
UserLoggedIn = 'USER_LOGGED_IN'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMessageFromCode = (resultCode: string) => {
|
||||||
|
switch (resultCode) {
|
||||||
|
case ResultCode.InvalidCredentials:
|
||||||
|
return 'Invalid credentials!'
|
||||||
|
case ResultCode.InvalidSubmission:
|
||||||
|
return 'Invalid submission, please try again!'
|
||||||
|
case ResultCode.UserAlreadyExists:
|
||||||
|
return 'User already exists, please log in!'
|
||||||
|
case ResultCode.UserCreated:
|
||||||
|
return 'User created, welcome!'
|
||||||
|
case ResultCode.UnknownError:
|
||||||
|
return 'Something went wrong, please try again!'
|
||||||
|
case ResultCode.UserLoggedIn:
|
||||||
|
return 'Logged in!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
"@vercel/analytics": "^1.1.2",
|
"@vercel/analytics": "^1.1.2",
|
||||||
"@vercel/kv": "^1.0.1",
|
"@vercel/kv": "^1.0.1",
|
||||||
"@vercel/og": "^0.6.2",
|
"@vercel/og": "^0.6.2",
|
||||||
"@vercel/postgres": "^0.7.2",
|
|
||||||
"ai": "^3.0.12",
|
"ai": "^3.0.12",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
|
|
||||||
106
pnpm-lock.yaml
generated
106
pnpm-lock.yaml
generated
|
|
@ -44,9 +44,6 @@ dependencies:
|
||||||
'@vercel/og':
|
'@vercel/og':
|
||||||
specifier: ^0.6.2
|
specifier: ^0.6.2
|
||||||
version: 0.6.2
|
version: 0.6.2
|
||||||
'@vercel/postgres':
|
|
||||||
specifier: ^0.7.2
|
|
||||||
version: 0.7.2
|
|
||||||
ai:
|
ai:
|
||||||
specifier: ^3.0.12
|
specifier: ^3.0.12
|
||||||
version: 3.0.12(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4)
|
version: 3.0.12(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4)
|
||||||
|
|
@ -384,12 +381,6 @@ packages:
|
||||||
'@jridgewell/resolve-uri': 3.1.1
|
'@jridgewell/resolve-uri': 3.1.1
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
|
|
||||||
/@neondatabase/serverless@0.7.2:
|
|
||||||
resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==}
|
|
||||||
dependencies:
|
|
||||||
'@types/pg': 8.6.6
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@next/env@14.1.3:
|
/@next/env@14.1.3:
|
||||||
resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==}
|
resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
@ -1333,14 +1324,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 5.26.5
|
undici-types: 5.26.5
|
||||||
|
|
||||||
/@types/pg@8.6.6:
|
|
||||||
resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 20.11.5
|
|
||||||
pg-protocol: 1.6.0
|
|
||||||
pg-types: 2.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/prop-types@15.7.11:
|
/@types/prop-types@15.7.11:
|
||||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||||
|
|
||||||
|
|
@ -1465,16 +1448,6 @@ packages:
|
||||||
yoga-wasm-web: 0.3.3
|
yoga-wasm-web: 0.3.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@vercel/postgres@0.7.2:
|
|
||||||
resolution: {integrity: sha512-IqR/ZAvoPGcPaXl9eWWB5KaA+w/81RzZa/18P4izQRHpNBkTGt9HwGfYi9+wut5UgxNq4QSX9A7HIQR6QDvX2Q==}
|
|
||||||
engines: {node: '>=14.6'}
|
|
||||||
dependencies:
|
|
||||||
'@neondatabase/serverless': 0.7.2
|
|
||||||
bufferutil: 4.0.8
|
|
||||||
utf-8-validate: 6.0.3
|
|
||||||
ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@vue/compiler-core@3.4.15:
|
/@vue/compiler-core@3.4.15:
|
||||||
resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
|
resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -1857,14 +1830,6 @@ packages:
|
||||||
update-browserslist-db: 1.0.13(browserslist@4.22.2)
|
update-browserslist-db: 1.0.13(browserslist@4.22.2)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/bufferutil@4.0.8:
|
|
||||||
resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
|
|
||||||
engines: {node: '>=6.14.2'}
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
node-gyp-build: 4.8.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/busboy@1.6.0:
|
/busboy@1.6.0:
|
||||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||||
engines: {node: '>=10.16.0'}
|
engines: {node: '>=10.16.0'}
|
||||||
|
|
@ -4125,11 +4090,6 @@ packages:
|
||||||
whatwg-url: 5.0.0
|
whatwg-url: 5.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/node-gyp-build@4.8.0:
|
|
||||||
resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==}
|
|
||||||
hasBin: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/node-releases@2.0.14:
|
/node-releases@2.0.14:
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -4337,26 +4297,6 @@ packages:
|
||||||
is-reference: 3.0.2
|
is-reference: 3.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/pg-int8@1.0.1:
|
|
||||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
|
||||||
engines: {node: '>=4.0.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/pg-protocol@1.6.0:
|
|
||||||
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/pg-types@2.2.0:
|
|
||||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
dependencies:
|
|
||||||
pg-int8: 1.0.1
|
|
||||||
postgres-array: 2.0.0
|
|
||||||
postgres-bytea: 1.0.0
|
|
||||||
postgres-date: 1.0.7
|
|
||||||
postgres-interval: 1.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/picocolors@1.0.0:
|
/picocolors@1.0.0:
|
||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
|
|
||||||
|
|
@ -4460,28 +4400,6 @@ packages:
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
|
|
||||||
/postgres-array@2.0.0:
|
|
||||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postgres-bytea@1.0.0:
|
|
||||||
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postgres-date@1.0.7:
|
|
||||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postgres-interval@1.2.0:
|
|
||||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dependencies:
|
|
||||||
xtend: 4.0.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/preact-render-to-string@5.2.3(preact@10.11.3):
|
/preact-render-to-string@5.2.3(preact@10.11.3):
|
||||||
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
|
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -5528,14 +5446,6 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/utf-8-validate@6.0.3:
|
|
||||||
resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
|
|
||||||
engines: {node: '>=6.14.2'}
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
node-gyp-build: 4.8.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -5682,22 +5592,6 @@ packages:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3):
|
|
||||||
resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: '>=5.0.2'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
bufferutil: 4.0.8
|
|
||||||
utf-8-validate: 6.0.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/xtend@4.0.2:
|
/xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue