Added zod and use drizzle to get and add bookmarks

master
TZGyn 2 years ago
parent d2d3c43842
commit 173d738aa4
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

@ -0,0 +1,2 @@
NEXT_APP_URL=https://next-dashboard.tzgyn.com
DATABASE_URL=postgres://postgres:password@127.0.0.1:5432/next-dashboard

@ -0,0 +1,25 @@
import { bookmarkSchema } from '@/types'
import { NextRequest, NextResponse } from 'next/server'
import { db, bookmark } from '@/lib/schema'
export const dynamic = 'force-dynamic'
export const GET = async () => {
const bookmarks = await db.select().from(bookmark)
return NextResponse.json(bookmarks)
}
export const POST = async (request: NextRequest) => {
const body = await request.json()
const newBookmark = bookmarkSchema.safeParse(body)
if (newBookmark.success) {
await db.insert(bookmark).values(newBookmark.data)
return NextResponse.json({ status: 200 })
}
return NextResponse.json(newBookmark.error)
}

@ -1,5 +0,0 @@
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello World' }, { status: 200 })
}

@ -1,90 +1,26 @@
'use client'
import { Card, CardHeader, CardBody, CardFooter } from '@nextui-org/card'
import { Link } from '@nextui-org/link'
import { Divider } from '@nextui-org/divider'
import { Image } from '@nextui-org/image'
const handleClick = (url: string) => {
console.log(url)
// open(url, '_blank')
getData()
}
import { BookmarkCard } from '@/components/bookmarkCard'
import { bookmarkSchema } from '@/types'
const getData = async () => {
const res = await fetch('/api/bruh')
const res = await fetch(process.env.NEXT_APP_URL + '/api/bookmark', {
method: 'GET',
cache: 'no-store',
})
const data = await res.json()
console.log(res)
return bookmarkSchema.array().parse(data)
}
export default function DashboardPage() {
const data = [
{
name: 'NextUI',
link: 'nextui.org',
description:
'Make beautiful websites regardless of your design experience.',
url: 'https://github.com/nextui-org/nextui',
},
{
name: 'NextUI',
link: 'nextui.org',
description:
'Make beautiful websites regardless of your design experience.',
url: 'https://github.com/nextui-org/nextui',
},
{
name: 'NextUI',
link: 'nextui.org',
description:
'Make beautiful websites regardless of your design experience.',
url: 'https://github.com/nextui-org/nextui',
},
{
name: 'NextUI',
link: 'nextui.org',
description:
'Make beautiful websites regardless of your design experience.',
url: 'https://github.com/nextui-org/nextui',
},
]
export default async function DashboardPage() {
const data = await getData()
return (
<div className='mt-4 flex w-full max-w-4xl flex-row flex-wrap justify-center gap-6'>
{data.map((data, index) => (
<Card
className='max-w-[400px]'
key={index}
isPressable
onPress={() => handleClick(data.url)}>
<CardHeader className='flex gap-3'>
<Image
alt='nextui logo'
height={40}
radius='sm'
src='https://avatars.githubusercontent.com/u/86160567?s=200&v=4'
width={40}
/>
<div className='flex flex-col items-start'>
<p className='text-md'>{data.name}</p>
<p className='text-small text-default-500'>
{data.link}
</p>
</div>
</CardHeader>
<Divider />
<CardBody>
<p>{data.description}</p>
</CardBody>
<Divider />
<CardFooter>
<Link
isExternal
showAnchorIcon
href={data.url}>
{data.url}
</Link>
</CardFooter>
</Card>
<BookmarkCard
index={index}
data={data}
/>
))}
</div>
)

@ -0,0 +1,55 @@
'use client'
import { Card, CardHeader, CardBody, CardFooter } from '@nextui-org/card'
import { Link } from '@nextui-org/link'
import { Divider } from '@nextui-org/divider'
import { Image } from '@nextui-org/image'
import { type Bookmark } from '@/types'
export const BookmarkCard = ({
index,
data,
}: {
index: number
data: Bookmark
}) => {
return (
<>
<Card
className='w-96'
key={index}
isPressable
onPress={() => open(data.url, '_blank')}>
<CardHeader className='flex gap-3'>
<Image
alt='nextui logo'
height={40}
radius='sm'
src='https://avatars.githubusercontent.com/u/86160567?s=200&v=4'
width={40}
/>
<div className='flex flex-col items-start'>
<p className='text-md'>{data.name}</p>
<p className='text-small text-default-500'>
{data.link}
</p>
</div>
</CardHeader>
<Divider />
<CardBody>
<p>{data.description}</p>
</CardBody>
<Divider />
<CardFooter>
<Link
isExternal
showAnchorIcon
href={data.url}>
{data.url}
</Link>
</CardFooter>
</Card>
</>
)
}

@ -11,15 +11,33 @@ import {
import { Button } from '@nextui-org/button'
import { Input } from '@nextui-org/input'
import { Icon } from '@iconify/react'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
export default function NewBookmarkForm() {
const { isOpen, onOpen, onOpenChange } = useDisclosure()
const onSubmit = () => {
console.log('submitting')
const router = useRouter()
const onSubmit = async () => {
const body = {
name,
link,
description,
url,
}
await fetch('/api/bookmark', {
method: 'POST',
body: JSON.stringify(body),
})
onOpenChange()
router.refresh()
}
const [name, setName] = useState('')
const [link, setLink] = useState('')
const [description, setDescription] = useState('')
const [url, setUrl] = useState('')
return (
<>
<Button onPress={onOpen}>
@ -52,21 +70,37 @@ export default function NewBookmarkForm() {
label='Name'
placeholder='Enter Name'
variant='bordered'
value={name}
onChange={(event) => {
setName(event.target.value)
}}
/>
<Input
label='Link'
placeholder='Enter Link'
variant='bordered'
value={link}
onChange={(event) => {
setLink(event.target.value)
}}
/>
<Input
label='Description'
placeholder='Enter Desription'
variant='bordered'
value={description}
onChange={(event) => {
setDescription(event.target.value)
}}
/>
<Input
label='Url'
placeholder='Enter Url'
variant='bordered'
value={url}
onChange={(event) => {
setUrl(event.target.value)
}}
/>
</ModalBody>
<ModalFooter>

@ -1,7 +1,7 @@
import type { Config } from 'drizzle-kit'
export default {
schema: './schema.ts',
schema: './lib/schema.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {

@ -0,0 +1,46 @@
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { relations } from 'drizzle-orm'
import {
pgTable,
serial,
text,
varchar,
timestamp,
integer,
} from 'drizzle-orm/pg-core'
const client = postgres(
'postgres://postgres:password@127.0.0.1:5432/next-dashboard'
)
export const db = drizzle(client)
// export const user = pgTable('users', {
// id: serial('id').primaryKey(),
// name: text('name'),
// email: text('email'),
// password: text('password'),
// createdAt: timestamp('created_at'),
// updatedAt: timestamp('updated_at'),
// })
//
// export const userRelations = relations(user, ({ many }) => ({
// bookmark: many(bookmark),
// }))
//
export const bookmark = pgTable('bookmarks', {
id: serial('id').primaryKey(),
// userId: integer('user_id').references(() => user.id),
name: text('name'),
link: text('link'),
description: text('description'),
url: text('url'),
})
// export const bookmarkRelations = relations(bookmark, ({ one }) => ({
// user: one(user, {
// fields: [bookmark.userId],
// references: [user.id],
// }),
// }))

@ -38,17 +38,17 @@
"intl-messageformat": "^10.1.0",
"next": "13.4.9",
"next-themes": "^0.2.1",
"pg": "^8.11.2",
"postcss": "8.4.24",
"postgres": "^3.3.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwind-variants": "^0.1.13",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
"typescript": "5.0.4",
"zod": "^3.21.4"
},
"devDependencies": {
"@iconify/react": "^4.1.1",
"@types/pg": "^8.10.2",
"drizzle-kit": "^0.19.12",
"prettier": "^3.0.1",
"prettier-plugin-tailwindcss": "^0.4.1"

@ -73,7 +73,7 @@ dependencies:
version: 1.2.1
drizzle-orm:
specifier: ^0.28.1
version: 0.28.1(@types/pg@8.10.2)(pg@8.11.2)
version: 0.28.1(postgres@3.3.5)
eslint:
specifier: 8.41.0
version: 8.41.0
@ -92,12 +92,12 @@ dependencies:
next-themes:
specifier: ^0.2.1
version: 0.2.1(next@13.4.9)(react-dom@18.2.0)(react@18.2.0)
pg:
specifier: ^8.11.2
version: 8.11.2
postcss:
specifier: 8.4.24
version: 8.4.24
postgres:
specifier: ^3.3.5
version: 3.3.5
react:
specifier: 18.2.0
version: 18.2.0
@ -113,14 +113,14 @@ dependencies:
typescript:
specifier: 5.0.4
version: 5.0.4
zod:
specifier: ^3.21.4
version: 3.21.4
devDependencies:
'@iconify/react':
specifier: ^4.1.1
version: 4.1.1(react@18.2.0)
'@types/pg':
specifier: ^8.10.2
version: 8.10.2
drizzle-kit:
specifier: ^0.19.12
version: 0.19.12
@ -2164,13 +2164,7 @@ packages:
/@types/node@20.2.5:
resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==}
/@types/pg@8.10.2:
resolution: {integrity: sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==}
dependencies:
'@types/node': 20.2.5
pg-protocol: 1.6.0
pg-types: 4.0.1
dev: false
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
@ -2484,11 +2478,6 @@ packages:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
/buffer-writer@2.0.0:
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
engines: {node: '>=4'}
dev: false
/bundle-name@3.0.0:
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
engines: {node: '>=12'}
@ -2784,7 +2773,7 @@ packages:
- supports-color
dev: true
/drizzle-orm@0.28.1(@types/pg@8.10.2)(pg@8.11.2):
/drizzle-orm@0.28.1(postgres@3.3.5):
resolution: {integrity: sha512-6ms2pVxvkBJtuP1BZTUCzLLkr+iK/cNgd+tCw+I5/PoM8PB9kXaYoW9yNe/cnaXb0TLJ1gDEndDIyQdRX7zCdQ==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
@ -2846,8 +2835,7 @@ packages:
sqlite3:
optional: true
dependencies:
'@types/pg': 8.10.2
pg: 8.11.2
postgres: 3.3.5
dev: false
/electron-to-chromium@1.4.482:
@ -4304,9 +4292,6 @@ packages:
es-abstract: 1.22.1
dev: false
/obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@ -4362,10 +4347,6 @@ packages:
p-limit: 3.1.0
dev: false
/packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
dev: false
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -4402,84 +4383,6 @@ packages:
engines: {node: '>=8'}
dev: false
/pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
requiresBuild: true
dev: false
optional: true
/pg-connection-string@2.6.2:
resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
dev: false
/pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
/pg-numeric@1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
/pg-pool@3.6.1(pg@8.11.2):
resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==}
peerDependencies:
pg: '>=8.0'
dependencies:
pg: 8.11.2
dev: false
/pg-protocol@1.6.0:
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
/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
/pg-types@4.0.1:
resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
engines: {node: '>=10'}
dependencies:
pg-int8: 1.0.1
pg-numeric: 1.0.2
postgres-array: 3.0.2
postgres-bytea: 3.0.0
postgres-date: 2.0.1
postgres-interval: 3.0.0
postgres-range: 1.1.3
/pg@8.11.2:
resolution: {integrity: sha512-l4rmVeV8qTIrrPrIR3kZQqBgSN93331s9i6wiUiLOSk0Q7PmUxZD/m1rQI622l3NfqBby9Ar5PABfS/SulfieQ==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.6.2
pg-pool: 3.6.1(pg@8.11.2)
pg-protocol: 1.6.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
dev: false
/pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
dependencies:
split2: 4.2.0
dev: false
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: false
@ -4578,49 +4481,10 @@ packages:
source-map-js: 1.0.2
dev: false
/postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
/postgres@3.3.5:
resolution: {integrity: sha512-+JD93VELV9gHkqpV5gdL5/70HdGtEw4/XE1S4BC8f1mcPmdib3K5XsKVbnR1XcAyC41zOnifJ+9YRKxdIsXiUw==}
dev: false
/postgres-array@3.0.2:
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
engines: {node: '>=12'}
/postgres-bytea@1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
dev: false
/postgres-bytea@3.0.0:
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
engines: {node: '>= 6'}
dependencies:
obuf: 1.1.2
/postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
dev: false
/postgres-date@2.0.1:
resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==}
engines: {node: '>=12'}
/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
/postgres-interval@3.0.0:
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
engines: {node: '>=12'}
/postgres-range@1.1.3:
resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==}
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -4974,11 +4838,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
dev: false
/streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@ -5431,11 +5290,6 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
dev: false
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: false

@ -1,38 +0,0 @@
import { relations } from 'drizzle-orm'
import {
pgTable,
serial,
text,
varchar,
timestamp,
integer,
} from 'drizzle-orm/pg-core'
export const user = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
email: text('email'),
password: text('password'),
createdAt: timestamp('created_at'),
updatedAt: timestamp('updated_at'),
})
export const userRelations = relations(user, ({ many }) => ({
bookmark: many(bookmark),
}))
export const bookmark = pgTable('bookmarks', {
id: serial('id').primaryKey(),
userId: integer('user_id').references(() => user.id),
name: text('name'),
link: text('link'),
description: text('description'),
url: text('url'),
})
export const bookmarkRelations = relations(bookmark, ({ one }) => ({
user: one(user, {
fields: [bookmark.userId],
references: [user.id],
}),
}))

@ -1,5 +1,15 @@
import {SVGProps} from "react";
import { SVGProps } from 'react'
import { z } from 'zod'
export type IconSvgProps = SVGProps<SVGSVGElement> & {
size?: number;
};
size?: number
}
export const bookmarkSchema = z.object({
name: z.string(),
link: z.string(),
description: z.string(),
url: z.string().url(),
})
export type Bookmark = z.infer<typeof bookmarkSchema>

Loading…
Cancel
Save