diff options
| author | Dhravya Shah <[email protected]> | 2024-06-18 17:58:46 -0500 |
|---|---|---|
| committer | Dhravya Shah <[email protected]> | 2024-06-18 17:58:46 -0500 |
| commit | f4bb71e8f7e07bb2e919b7f222d5acb2905eb8f2 (patch) | |
| tree | 7310dc521ef3559055bbe71f50c3861be2fa0503 /apps/web/app/api | |
| parent | darkmode by default - so that the colors don't f up on lightmode devices (diff) | |
| parent | Create Embeddings for Canvas (diff) | |
| download | supermemory-default-darkmode.tar.xz supermemory-default-darkmode.zip | |
Diffstat (limited to 'apps/web/app/api')
| -rw-r--r-- | apps/web/app/api/[...nextauth]/route.ts | 2 | ||||
| -rw-r--r-- | apps/web/app/api/chat/route.ts | 103 | ||||
| -rw-r--r-- | apps/web/app/api/editorai/route.ts | 20 | ||||
| -rw-r--r-- | apps/web/app/api/ensureAuth.ts | 4 | ||||
| -rw-r--r-- | apps/web/app/api/getCount/route.ts | 8 | ||||
| -rw-r--r-- | apps/web/app/api/me/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/app/api/spaces/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/app/api/store/route.ts | 31 | ||||
| -rw-r--r-- | apps/web/app/api/unfirlsite/route.ts | 134 |
9 files changed, 236 insertions, 74 deletions
diff --git a/apps/web/app/api/[...nextauth]/route.ts b/apps/web/app/api/[...nextauth]/route.ts index 50807ab1..e19cc16e 100644 --- a/apps/web/app/api/[...nextauth]/route.ts +++ b/apps/web/app/api/[...nextauth]/route.ts @@ -1,2 +1,2 @@ -export { GET, POST } from "../../helpers/server/auth"; +export { GET, POST } from "../../../server/auth"; export const runtime = "edge"; diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index 34099848..c19ce92b 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -1,6 +1,11 @@ import { type NextRequest } from "next/server"; -import { ChatHistory } from "@repo/shared-types"; +import { + ChatHistory, + ChatHistoryZod, + convertChatHistoryList, +} from "@repo/shared-types"; import { ensureAuth } from "../ensureAuth"; +import { z } from "zod"; export const runtime = "edge"; @@ -15,59 +20,69 @@ export async function POST(req: NextRequest) { return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 }); } - const query = new URL(req.url).searchParams.get("q"); - const spaces = new URL(req.url).searchParams.get("spaces"); + const url = new URL(req.url); - const sourcesOnly = - new URL(req.url).searchParams.get("sourcesOnly") ?? "false"; + const query = url.searchParams.get("q"); + const spaces = url.searchParams.get("spaces"); - const chatHistory = (await req.json()) as { - chatHistory: ChatHistory[]; - }; + const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false"; - console.log("CHathistory", chatHistory); + const chatHistory = await req.json(); - if (!query) { + if (!query || query.trim.length < 0) { return new Response(JSON.stringify({ message: "Invalid query" }), { status: 400, }); } - try { - const resp = await fetch( - `https://cf-ai-backend.dhravya.workers.dev/chat?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, - { - headers: { - "X-Custom-Auth-Key": process.env.BACKEND_SECURITY_KEY!, - }, - method: "POST", - body: JSON.stringify({ - chatHistory: chatHistory.chatHistory ?? [], - }), + const validated = z + .object({ chatHistory: z.array(ChatHistoryZod) }) + .safeParse(chatHistory ?? []); + + if (!validated.success) { + return new Response( + JSON.stringify({ + message: "Invalid chat history", + error: validated.error, + }), + { status: 400 }, + ); + } + + const modelCompatible = await convertChatHistoryList( + validated.data.chatHistory, + ); + + const resp = await fetch( + `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.id}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, + { + headers: { + Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`, + "Content-Type": "application/json", }, + method: "POST", + body: JSON.stringify({ + chatHistory: modelCompatible, + }), + }, + ); + + console.log("sourcesOnly", sourcesOnly); + + if (sourcesOnly == "true") { + const data = await resp.json(); + console.log("data", data); + return new Response(JSON.stringify(data), { status: 200 }); + } + + if (resp.status !== 200 || !resp.ok) { + const errorData = await resp.text(); + console.log(errorData); + return new Response( + JSON.stringify({ message: "Error in CF function", error: errorData }), + { status: resp.status }, ); + } - console.log("sourcesOnly", sourcesOnly); - - if (sourcesOnly == "true") { - const data = await resp.json(); - console.log("data", data); - return new Response(JSON.stringify(data), { status: 200 }); - } - - if (resp.status !== 200 || !resp.ok) { - const errorData = await resp.json(); - console.log(errorData); - return new Response( - JSON.stringify({ message: "Error in CF function", error: errorData }), - { status: resp.status }, - ); - } - - // Stream the response back to the client - const { readable, writable } = new TransformStream(); - resp && resp.body!.pipeTo(writable); - - return new Response(readable, { status: 200 }); - } catch {} + return new Response(resp.body, { status: 200 }); } diff --git a/apps/web/app/api/editorai/route.ts b/apps/web/app/api/editorai/route.ts new file mode 100644 index 00000000..6ee0aed2 --- /dev/null +++ b/apps/web/app/api/editorai/route.ts @@ -0,0 +1,20 @@ +import type { NextRequest } from "next/server"; +import { ensureAuth } from "../ensureAuth"; + +export const runtime = "edge"; + +export async function POST(request: NextRequest) { + const d = await ensureAuth(request); + if (!d) { + return new Response("Unauthorized", { status: 401 }); + } + const res : {context: string, request: string} = await request.json() + + try { + const response = await fetch(`${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`); + const result = await response.json(); + return new Response(JSON.stringify(result)); + } catch (error) { + return new Response(`Error, ${error}`) + } +}
\ No newline at end of file diff --git a/apps/web/app/api/ensureAuth.ts b/apps/web/app/api/ensureAuth.ts index a1401a07..d2fbac0b 100644 --- a/apps/web/app/api/ensureAuth.ts +++ b/apps/web/app/api/ensureAuth.ts @@ -1,6 +1,6 @@ import { NextRequest } from "next/server"; -import { db } from "../helpers/server/db"; -import { sessions, users } from "../helpers/server/db/schema"; +import { db } from "../../server/db"; +import { sessions, users } from "../../server/db/schema"; import { eq } from "drizzle-orm"; export async function ensureAuth(req: NextRequest) { diff --git a/apps/web/app/api/getCount/route.ts b/apps/web/app/api/getCount/route.ts index f760c145..7cd2a2d3 100644 --- a/apps/web/app/api/getCount/route.ts +++ b/apps/web/app/api/getCount/route.ts @@ -1,6 +1,6 @@ -import { db } from "@/app/helpers/server/db"; +import { db } from "@/server/db"; import { and, eq, ne, sql } from "drizzle-orm"; -import { sessions, storedContent, users } from "@/app/helpers/server/db/schema"; +import { sessions, storedContent, users } from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; import { ensureAuth } from "../ensureAuth"; @@ -20,7 +20,7 @@ export async function GET(req: NextRequest) { .from(storedContent) .where( and( - eq(storedContent.user, session.user.id), + eq(storedContent.userId, session.user.id), eq(storedContent.type, "twitter-bookmark"), ), ); @@ -32,7 +32,7 @@ export async function GET(req: NextRequest) { .from(storedContent) .where( and( - eq(storedContent.user, session.user.id), + eq(storedContent.userId, session.user.id), ne(storedContent.type, "twitter-bookmark"), ), ); diff --git a/apps/web/app/api/me/route.ts b/apps/web/app/api/me/route.ts index 20b6aece..621dcbfe 100644 --- a/apps/web/app/api/me/route.ts +++ b/apps/web/app/api/me/route.ts @@ -1,6 +1,6 @@ -import { db } from "@/app/helpers/server/db"; +import { db } from "@/server/db"; import { eq } from "drizzle-orm"; -import { sessions, users } from "@/app/helpers/server/db/schema"; +import { sessions, users } from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; export const runtime = "edge"; diff --git a/apps/web/app/api/spaces/route.ts b/apps/web/app/api/spaces/route.ts index c46b02fc..cbed547d 100644 --- a/apps/web/app/api/spaces/route.ts +++ b/apps/web/app/api/spaces/route.ts @@ -1,5 +1,5 @@ -import { db } from "@/app/helpers/server/db"; -import { sessions, space, users } from "@/app/helpers/server/db/schema"; +import { db } from "@/server/db"; +import { sessions, space, users } from "@/server/db/schema"; import { eq } from "drizzle-orm"; import { NextRequest, NextResponse } from "next/server"; import { ensureAuth } from "../ensureAuth"; diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts index f96f90cf..cb10db24 100644 --- a/apps/web/app/api/store/route.ts +++ b/apps/web/app/api/store/route.ts @@ -1,4 +1,4 @@ -import { db } from "@/app/helpers/server/db"; +import { db } from "@/server/db"; import { and, eq, sql, inArray } from "drizzle-orm"; import { contentToSpace, @@ -6,10 +6,12 @@ import { storedContent, users, space, -} from "@/app/helpers/server/db/schema"; +} from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; -import { getMetaData } from "@/app/helpers/lib/get-metadata"; +import { getMetaData } from "@/lib/get-metadata"; import { ensureAuth } from "../ensureAuth"; +import { limit } from "@/app/actions/doers"; +import { LIMITS } from "@/lib/constants"; export const runtime = "edge"; @@ -33,22 +35,13 @@ export async function POST(req: NextRequest) { storeToSpaces = []; } - const count = await db - .select({ - count: sql<number>`count(*)`.mapWith(Number), - }) - .from(storedContent) - .where( - and( - eq(storedContent.user, session.user.id), - eq(storedContent.type, "page"), - ), - ); - - if (count[0]!.count > 100) { + if (!(await limit(session.user.id))) { return NextResponse.json( - { message: "Error", error: "Limit exceeded" }, - { status: 499 }, + { + message: "Error: Ratelimit exceeded", + error: `You have exceeded the limit of ${LIMITS["page"]} pages.`, + }, + { status: 429 }, ); } @@ -62,7 +55,7 @@ export async function POST(req: NextRequest) { baseUrl: metadata.baseUrl, image: metadata.image, savedAt: new Date(), - user: session.user.id, + userId: session.user.id, }) .returning({ id: storedContent.id }); diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts new file mode 100644 index 00000000..4b8b4858 --- /dev/null +++ b/apps/web/app/api/unfirlsite/route.ts @@ -0,0 +1,134 @@ +import { load } from 'cheerio' +import { AwsClient } from "aws4fetch"; + +import type { NextRequest } from "next/server"; +import { ensureAuth } from "../ensureAuth"; + +export const runtime = "edge"; + +const r2 = new AwsClient({ + accessKeyId: process.env.R2_ACCESS_KEY_ID, + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, +}); + + +export async function POST(request: NextRequest) { + + const d = await ensureAuth(request); + if (!d) { + return new Response("Unauthorized", { status: 401 }); + } + + if ( + !process.env.R2_ACCESS_KEY_ID || + !process.env.R2_ACCOUNT_ID || + !process.env.R2_SECRET_ACCESS_KEY || + !process.env.R2_BUCKET_NAME + ) { + return new Response( + "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.", + { status: 500 }, + ); + } + + const website = new URL(request.url).searchParams.get("website"); + + if (!website) { + return new Response("Missing website", { status: 400 }); + } + + const salt = () => Math.floor(Math.random() * 11); + const encodeWebsite = `${encodeURIComponent(website)}${salt()}`; + + try { + // this returns the og image, description and title of website + const response = await unfurl(website); + + if (!response.image){ + return new Response(JSON.stringify(response)) + } + + const imageUrl = await process.env.DEV_IMAGES.get(encodeWebsite) + if (imageUrl){ + return new Response(JSON.stringify({ + image: imageUrl, + title: response.title, + description: response.description, + })) + } + + const res = await fetch(`${response.image}`) + const image = await res.blob(); + + const url = new URL( + `https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com` + ); + + url.pathname = encodeWebsite; + url.searchParams.set("X-Amz-Expires", "3600"); + + const signedPuturl = await r2.sign( + new Request(url, { + method: "PUT", + }), + { + aws: { signQuery: true }, + } + ); + await fetch(signedPuturl.url, { + method: 'PUT', + body: image, + }); + + await process.env.DEV_IMAGES.put(encodeWebsite, `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`) + + return new Response(JSON.stringify({ + image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`, + title: response.title, + description: response.description, + })); + + } catch (error) { + console.log(error) + return new Response(JSON.stringify({ + status: 500, + error: error, + })) + } + } + +export async function unfurl(url: string) { + const response = await fetch(url) + if (response.status >= 400) { + throw new Error(`Error fetching url: ${response.status}`) + } + const contentType = response.headers.get('content-type') + if (!contentType?.includes('text/html')) { + throw new Error(`Content-type not right: ${contentType}`) + } + + const content = await response.text() + const $ = load(content) + + const og: { [key: string]: string | undefined } = {} + const twitter: { [key: string]: string | undefined } = {} + + // @ts-ignore, it just works so why care of type safety if someone has better way go ahead + $('meta[property^=og:]').each((_, el) => (og[$(el).attr('property')!] = $(el).attr('content'))) + // @ts-ignore + $('meta[name^=twitter:]').each((_, el) => (twitter[$(el).attr('name')!] = $(el).attr('content'))) + + const title = og['og:title'] ?? twitter['twitter:title'] ?? $('title').text() ?? undefined + const description = + og['og:description'] ?? + twitter['twitter:description'] ?? + $('meta[name="description"]').attr('content') ?? + undefined + const image = og['og:image:secure_url'] ?? og['og:image'] ?? twitter['twitter:image'] ?? undefined + + return { + title, + description, + image, + } +} |