diff --git a/.env.local.demo b/.env.local.demo index cfbb51e..2100f1c 100644 --- a/.env.local.demo +++ b/.env.local.demo @@ -32,4 +32,8 @@ GITHUB_SECRET= # NEXT-AUTH Google Configure. https://next-auth.js.org/providers/google GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= \ No newline at end of file +GOOGLE_CLIENT_SECRET= + +# Redis Configure +UPSTASH_REDIS_REST_URL= +UPSTASH_REDIS_REST_TOKEN= \ No newline at end of file diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index eafa6d7..73ae8ab 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,5 +1,19 @@ # L-GPT Change Log +## v0.5.0 + +> 2023-05-30 + +### Add + +- Added conversation sharing function + +### Changed + +- Move all Prisma variable definitions to .env.local +- Standardize the global logo style +- Multiple detailed optimizations + ## v0.4.3 > 2023-05-28 diff --git a/CHANGE_LOG.zh_CN.md b/CHANGE_LOG.zh_CN.md index 4fb9a1f..10f3412 100644 --- a/CHANGE_LOG.zh_CN.md +++ b/CHANGE_LOG.zh_CN.md @@ -1,5 +1,19 @@ # L-GPT 更新日志 +## v0.5.0 + +> 2023-05-30 + +### Add + +- 新增会话分享功能 + +### Changed + +- 将 prisma 变量定义全部移动到.env.local +- 统一全局 Logo 样式 +- 多处细节优化 + ## v0.4.3 > 2023-05-28 diff --git a/README.md b/README.md index 024aa49..7f4093d 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Welcome to:[Telegram](https://t.me/+7fLJJoGV_bJhYTk1) - [x] Introduce prompt words and prompt word templates - [x] Chat record import and export - [x] Account System +- [x] Support conversation sharing - [ ] Support for customizing the prompt repository - [ ] Support GPT-4 and Claude - [ ] Compress context to save chat tokens diff --git a/README_CN.md b/README_CN.md index 996c6eb..3e63ceb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -27,6 +27,7 @@ L-GPT 是一款开源项目,通过提供不同的 AI 模型来帮助你提高 - [x] 引入提示词以及提示词模板 - [x] 聊天记录导入导出 - [x] 账号系统 +- [x] 支持会话分享 - [ ] 支持自定义 prompt 仓库 - [ ] 支持 GPT-4 和 Claude - [ ] 压缩上下文,节省聊天 token diff --git a/package.json b/package.json index 1bc0fb1..9ac321b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "l-gpt", - "version": "0.4.3", + "version": "0.5.0", "private": true, "scripts": { "dev": "next dev", @@ -8,9 +8,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "db-generate": "prisma generate", - "db-pull": "prisma db pull", - "db-push": "prisma db push" + "db-gn": "prisma generate", + "db-pull": "dotenv -e .env.local -- npx prisma db pull", + "db-push": "dotenv -e .env.local -- npx prisma db push" }, "dependencies": { "@emotion/css": "11.11.0", @@ -30,6 +30,7 @@ "@types/node": "20.2.5", "@types/react": "18.2.7", "@types/react-dom": "18.2.4", + "@upstash/redis": "^1.20.6", "@vercel/analytics": "1.0.1", "ahooks": "3.7.7", "autoprefixer": "10.4.14", @@ -46,7 +47,7 @@ "next-auth": "4.22.1", "next-intl": "2.14.6", "next-themes": "0.2.1", - "nodemailer": "6.9.2", + "nodemailer": "6.9.3", "postcss": "8.4.24", "react": "18.2.0", "react-dom": "18.2.0", @@ -69,6 +70,7 @@ "@types/math-random": "1.0.0", "@types/react-syntax-highlighter": "15.5.7", "@types/uuid": "9.0.1", + "dotenv-cli": "7.2.1", "prisma": "4.14.1" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8332c70..2d1ef5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,8 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: '@emotion/css': @@ -52,6 +56,9 @@ dependencies: '@types/react-dom': specifier: 18.2.4 version: 18.2.4 + '@upstash/redis': + specifier: ^1.20.6 + version: 1.20.6 '@vercel/analytics': specifier: 1.0.1 version: 1.0.1 @@ -93,7 +100,7 @@ dependencies: version: 13.4.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) next-auth: specifier: 4.22.1 - version: 4.22.1(next@13.4.3)(nodemailer@6.9.2)(react-dom@18.2.0)(react@18.2.0) + version: 4.22.1(next@13.4.3)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0) next-intl: specifier: 2.14.6 version: 2.14.6(next@13.4.3)(react@18.2.0) @@ -101,8 +108,8 @@ dependencies: specifier: 0.2.1 version: 0.2.1(next@13.4.3)(react-dom@18.2.0)(react@18.2.0) nodemailer: - specifier: 6.9.2 - version: 6.9.2 + specifier: 6.9.3 + version: 6.9.3 postcss: specifier: 8.4.24 version: 8.4.24 @@ -165,6 +172,9 @@ devDependencies: '@types/uuid': specifier: 9.0.1 version: 9.0.1 + dotenv-cli: + specifier: 7.2.1 + version: 7.2.1 prisma: specifier: 4.14.1 version: 4.14.1 @@ -1704,7 +1714,7 @@ packages: next-auth: ^4 dependencies: '@prisma/client': 4.14.1(prisma@4.14.1) - next-auth: 4.22.1(next@13.4.3)(nodemailer@6.9.2)(react-dom@18.2.0)(react@18.2.0) + next-auth: 4.22.1(next@13.4.3)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0) dev: false /@next/env@13.4.3: @@ -3165,6 +3175,14 @@ packages: eslint-visitor-keys: 3.4.1 dev: false + /@upstash/redis@1.20.6: + resolution: {integrity: sha512-q1izaYEUsq/WiXNOjf4oOjFZe8fIeBSZN8d5cEyOD4nem+zxc4jccieorQQrNlEahKPE1ZYLzVEkMODRUfch2g==} + dependencies: + isomorphic-fetch: 3.0.0 + transitivePeerDependencies: + - encoding + dev: false + /@vercel/analytics@1.0.1: resolution: {integrity: sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==} dev: false @@ -3672,7 +3690,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: false /css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} @@ -3899,6 +3916,26 @@ packages: tslib: 2.5.0 dev: false + /dotenv-cli@7.2.1: + resolution: {integrity: sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dotenv: 16.0.3 + dotenv-expand: 10.0.0 + minimist: 1.2.8 + dev: true + + /dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + dev: true + + /dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: true + /electron-to-chromium@1.4.387: resolution: {integrity: sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw==} dev: false @@ -5078,6 +5115,14 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + dependencies: + node-fetch: 2.6.11 + whatwg-fetch: 3.6.2 + transitivePeerDependencies: + - encoding dev: false /jiti@1.18.2: @@ -5745,7 +5790,6 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: false /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} @@ -5788,7 +5832,7 @@ packages: engines: {node: '>= 0.6'} dev: false - /next-auth@4.22.1(next@13.4.3)(nodemailer@6.9.2)(react-dom@18.2.0)(react@18.2.0): + /next-auth@4.22.1(next@13.4.3)(nodemailer@6.9.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==} peerDependencies: next: ^12.2.5 || ^13 @@ -5804,7 +5848,7 @@ packages: cookie: 0.5.0 jose: 4.14.4 next: 13.4.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0) - nodemailer: 6.9.2 + nodemailer: 6.9.3 oauth: 0.9.15 openid-client: 5.4.2 preact: 10.13.2 @@ -5908,8 +5952,8 @@ packages: resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} dev: false - /nodemailer@6.9.2: - resolution: {integrity: sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==} + /nodemailer@6.9.3: + resolution: {integrity: sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==} engines: {node: '>=6.0.0'} dev: false @@ -6135,7 +6179,6 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: false /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} @@ -6685,12 +6728,10 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: false /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: false /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -7277,6 +7318,10 @@ packages: engines: {node: '>=10.13.0'} dev: false + /whatwg-fetch@3.6.2: + resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==} + dev: false + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -7321,7 +7366,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: false /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index aad3195..8e10170 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -37,6 +37,15 @@ model Session { user User @relation(fields: [userId], references: [id], onDelete: Cascade) } +model VerificationToken { + id String @id @default(cuid()) + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} + model User { id String @id @default(cuid()) name String? @@ -48,13 +57,19 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt recentlyUse DateTime? + share Share[] } -model VerificationToken { - id String @id @default(cuid()) - identifier String - token String @unique - expires DateTime - - @@unique([identifier, token]) +model Share { + id String @id @default(cuid()) + channel_model Json + channel_name String? + channel_prompt String? + chat_content Json[] + anonymous Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime? @updatedAt + userId String? + userName String? + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) } diff --git a/src/app/[locale]/(create)/create/[id]/page.tsx b/src/app/[locale]/(create)/create/[id]/page.tsx new file mode 100644 index 0000000..a34863e --- /dev/null +++ b/src/app/[locale]/(create)/create/[id]/page.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/prisma"; +import type { Share } from "@prisma/client"; +import CreateChat from "@/components/share/createChat"; + +type Props = { + params: { id: string }; +}; + +async function getShareData(id: Share["id"]) { + const shareRes = await prisma.share.findUnique({ + where: { id }, + }); + + if (!shareRes) return null; + + return shareRes; +} + +export default async function Create({ params }: Props) { + const id = params.id; + if (!id) return notFound(); + + const content = await getShareData(id); + + if (!content) return notFound(); + + return ; +} diff --git a/src/app/[locale]/(create)/layout.tsx b/src/app/[locale]/(create)/layout.tsx new file mode 100644 index 0000000..ea1293f --- /dev/null +++ b/src/app/[locale]/(create)/layout.tsx @@ -0,0 +1,25 @@ +import { cn } from "@/lib"; +import Logo from "@/components/logo"; + +export default async function CreateLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ +
+
+
{children}
+
+
+ ); +} diff --git a/src/app/[locale]/(home)/page.tsx b/src/app/[locale]/(home)/page.tsx index ca60940..4ceafe1 100644 --- a/src/app/[locale]/(home)/page.tsx +++ b/src/app/[locale]/(home)/page.tsx @@ -18,8 +18,7 @@ export default function Home() {
diff --git a/src/app/[locale]/(share)/layout.tsx b/src/app/[locale]/(share)/layout.tsx new file mode 100644 index 0000000..d5d9890 --- /dev/null +++ b/src/app/[locale]/(share)/layout.tsx @@ -0,0 +1,27 @@ +import { cn } from "@/lib"; +import Logo from "@/components/logo"; +import BackHome from "@/components/share/backHome"; + +export default async function AuthenticationLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ + +
+
+
{children}
+
+
+ ); +} diff --git a/src/app/[locale]/(share)/share/[id]/opengraph-image.tsx b/src/app/[locale]/(share)/share/[id]/opengraph-image.tsx new file mode 100644 index 0000000..fa704f8 --- /dev/null +++ b/src/app/[locale]/(share)/share/[id]/opengraph-image.tsx @@ -0,0 +1,159 @@ +import { ImageResponse } from "next/server"; +import { AiOutlineUser } from "react-icons/ai"; + +const baseURL = + process.env.NODE_ENV === "development" + ? "http://localhost:3000" + : "https://gpt.ltopx.com"; + +// Route segment config +export const runtime = "edge"; + +// Image metadata +export const alt = "LGPT Share"; +export const size = { + width: 1200, + height: 630, +}; + +export const contentType = "image/png"; + +type Props = { + params: { id: string }; +}; + +async function getShareData(id: string) { + try { + const res = await fetch(`${baseURL}/api/share?id=${id}`).then((res) => + res.json() + ); + if (res.error) return null; + return res.data; + } catch (error) { + return null; + } +} + +// Image generation +export default async function Image({ params }: Props) { + const content = await getShareData(params.id); + const title = content?.channel_name || "Unnamed"; + const chat_list: any[] = content?.chat_content?.slice(0, 2) || []; + + return new ImageResponse( + ( +
+
+ {title} +
+
+ {chat_list.map((item) => ( +
+
1
+
+
+ {item.role === "user" ? ( + + ) : ( + // eslint-disable-next-line @next/next/no-img-element + gpt + )} +
+
+
+
+ {item.content} +
+
+
+ ))} +
+
+ Powered by + + LGPT + + + Share + +
+
+ ) + ); +} diff --git a/src/app/[locale]/(share)/share/[id]/page.tsx b/src/app/[locale]/(share)/share/[id]/page.tsx new file mode 100644 index 0000000..c7bc967 --- /dev/null +++ b/src/app/[locale]/(share)/share/[id]/page.tsx @@ -0,0 +1,165 @@ +import * as React from "react"; +import Image from "next/image"; +import { Metadata } from "next"; +import { cn } from "@/lib"; +import { prisma } from "@/lib/prisma"; +import { redis } from "@/lib/redis"; +import type { Share } from "@prisma/client"; +import { AiOutlineUser } from "react-icons/ai"; +import { SiMicrosoftazure } from "react-icons/si"; +import Button from "@/components/ui/Button"; +import CopyIcon from "@/components/copyIcon"; +import ChatContent from "@/components/chatContent"; +import BasicInfo from "@/components/share/basicInfo"; +import NotFound from "@/components/share/notFound"; +import Continue from "@/components/share/continue"; + +type Props = { + params: { id: string }; +}; + +export async function generateMetadata({ params }: Props): Promise { + const shareRes: any = await prisma.share.findUnique({ + where: { id: params.id }, + }); + + if (!shareRes) return {}; + + return { + title: "L-GPT Share", + description: + "L-GPT是一款开源项目,通过提供不同的AI模型来帮助你提高学习、工作、生活的效率。", + keywords: + "gpt,gpt-3.5-turbo,gpt-4,azure openai,claude,chat,聊天,LGPT,L-GPT", + openGraph: { + title: shareRes.channel_name, + description: "L-GPT Share Chat", + siteName: "L-GPT", + url: "https://gpt.ltopx.com", + }, + twitter: { + title: shareRes.channel_name, + description: "L-GPT Share Chat", + card: "summary_large_image", + site: "@peekbomb", + creator: "@peekbomb", + }, + }; +} + +async function getShareData(id: Share["id"]) { + const shareRes = await prisma.share.findUnique({ + where: { id }, + }); + + if (!shareRes) return { content: null, viewsCount: 0 }; + + const viewsCount: number | null = await redis.get(id); + await redis.set(id, viewsCount ? Number(viewsCount) + 1 : 1); + + return { content: shareRes, viewsCount: viewsCount ? viewsCount : 1 }; +} + +function getTime(timestamp: string) { + const date = new Date(Number(timestamp)); + let month: any = date.getMonth() + 1; + month = month < 10 ? "0" + month : month; + let day: any = date.getDate(); + day = day < 10 ? "0" + day : day; + let hour: any = date.getHours(); + hour = hour < 10 ? "0" + hour : hour; + let minute: any = date.getMinutes(); + minute = minute < 10 ? "0" + minute : minute; + let seconds: any = date.getSeconds(); + seconds = seconds < 10 ? "0" + seconds : seconds; + + return `${month}-${day} ${hour}:${minute}:${seconds}`; +} + +export default async function Share({ params }: any) { + const { content, viewsCount } = await getShareData(params.id); + if (!content) return ; + + const { userName, anonymous } = content; + + const shareFrom = userName && !anonymous ? userName : ""; + + return ( + <> +
+
+
+ {content.channel_name} +
+
+ +
+
+ +
+
+ +
+ {content.chat_content.map((item: any) => ( +
+
+ {item.role === "assistant" && ( +
+ gpt +
+ )} + {item.role === "user" && ( +
+ +
+ )} +
+
+
+ {getTime(item.time)} + +
+
+ +
+
+
+ ))} +
+
+
+ +
+ + ); +} diff --git a/src/app/api/share/create/route.ts b/src/app/api/share/create/route.ts new file mode 100644 index 0000000..ac58391 --- /dev/null +++ b/src/app/api/share/create/route.ts @@ -0,0 +1,72 @@ +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; +import type { ChatItem } from "@/hooks"; + +interface ChannelModel { + supplier: string; + type: string; +} + +export interface IShare { + channel_model: ChannelModel; + channel_name?: string; + channel_prompt?: string; + chat_content: ChatItem[]; + userId?: string; + userName?: string; +} + +export async function POST(request: Request) { + const session = await getServerSession(authOptions); + const { channel_model, channel_name, channel_prompt, chat_content }: IShare = + await request.json(); + + if (!channel_model) { + return NextResponse.json( + { error: -1, msg: "channel_model cannot be empty" }, + { status: 500 } + ); + } + + if (!chat_content?.length) { + return NextResponse.json( + { error: -1, msg: "chat_content cannot be empty" }, + { status: 500 } + ); + } + + // create share + let params: IShare = { + channel_model, + channel_name, + channel_prompt, + chat_content, + }; + + // 登录用户加上用户信息,但是可以选择匿名分享 + if (session) + params = { + ...params, + userId: session?.user.id, + userName: session?.user.name as string, + }; + + try { + const createShareRes = await prisma.share.create({ + data: params as any, + select: { id: true }, + }); + return NextResponse.json( + { error: 0, data: createShareRes }, + { status: 200 } + ); + } catch (error) { + console.log(error, "create share error"); + return NextResponse.json( + { error: -1, msg: "create share error" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/share/delete/route.ts b/src/app/api/share/delete/route.ts new file mode 100644 index 0000000..3f87523 --- /dev/null +++ b/src/app/api/share/delete/route.ts @@ -0,0 +1,24 @@ +import { prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function DELETE(request: Request) { + const { id } = await request.json(); + + if (!id) { + return NextResponse.json( + { error: -1, msg: "id cannot be empty" }, + { status: 500 } + ); + } + + try { + await prisma.share.delete({ + where: { id }, + }); + + return NextResponse.json({ error: 0 }, { status: 200 }); + } catch (error) { + console.log("delete share error", error); + return NextResponse.json({ error: -1, msg: "error" }, { status: 500 }); + } +} diff --git a/src/app/api/share/route.ts b/src/app/api/share/route.ts new file mode 100644 index 0000000..43afdb8 --- /dev/null +++ b/src/app/api/share/route.ts @@ -0,0 +1,25 @@ +import { prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + + const id = searchParams.get("id"); + + if (!id) { + return NextResponse.json( + { error: -1, msg: "id cannot be empty" }, + { status: 500 } + ); + } + + try { + const shareRes: any = await prisma.share.findUnique({ + where: { id }, + }); + + return NextResponse.json({ error: 0, data: shareRes }, { status: 200 }); + } catch (error) { + return NextResponse.json({ error: -1, msg: "error" }, { status: 500 }); + } +} diff --git a/src/app/api/share/update/route.ts b/src/app/api/share/update/route.ts new file mode 100644 index 0000000..5629b60 --- /dev/null +++ b/src/app/api/share/update/route.ts @@ -0,0 +1,25 @@ +import { prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function POST(request: Request) { + const { id, anonymous } = await request.json(); + + if (!id) { + return NextResponse.json( + { error: -1, msg: "id cannot be empty" }, + { status: 500 } + ); + } + + try { + await prisma.share.update({ + data: { anonymous: Number(!!anonymous) }, + where: { id }, + }); + + return NextResponse.json({ error: 0 }, { status: 200 }); + } catch (error) { + console.log("update share error", error); + return NextResponse.json({ error: -1, msg: "error" }, { status: 500 }); + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 09d78cf..8ba3cd6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,7 +10,7 @@ interface RootLayoutProps { children: React.ReactNode; } -// const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "L-GPT", @@ -24,7 +24,7 @@ export const metadata: Metadata = { export default function RootLayout({ children }: RootLayoutProps) { return ( - +