diff options
| author | Dhravya <[email protected]> | 2024-07-16 19:26:47 -0500 |
|---|---|---|
| committer | Dhravya <[email protected]> | 2024-07-16 19:26:47 -0500 |
| commit | 5b44dcb1d1f7fcea331b248800cad60129038eab (patch) | |
| tree | c5562fd33a95d09e625fea7586392eacfcc3fca7 /apps/web/app/api | |
| parent | breadcrumbs (diff) | |
| download | archived-supermemory-5b44dcb1d1f7fcea331b248800cad60129038eab.tar.xz archived-supermemory-5b44dcb1d1f7fcea331b248800cad60129038eab.zip | |
use tabs
Diffstat (limited to 'apps/web/app/api')
| -rw-r--r-- | apps/web/app/api/canvas/route.ts | 10 | ||||
| -rw-r--r-- | apps/web/app/api/canvasai/route.ts | 46 | ||||
| -rw-r--r-- | apps/web/app/api/chat/route.ts | 154 | ||||
| -rw-r--r-- | apps/web/app/api/editorai/route.ts | 44 | ||||
| -rw-r--r-- | apps/web/app/api/ensureAuth.ts | 42 | ||||
| -rw-r--r-- | apps/web/app/api/getCount/route.ts | 66 | ||||
| -rw-r--r-- | apps/web/app/api/hello/route.ts | 26 | ||||
| -rw-r--r-- | apps/web/app/api/me/route.ts | 66 | ||||
| -rw-r--r-- | apps/web/app/api/mobile/newUser/route.ts | 13 | ||||
| -rw-r--r-- | apps/web/app/api/spaces/route.ts | 32 | ||||
| -rw-r--r-- | apps/web/app/api/store/route.ts | 392 | ||||
| -rw-r--r-- | apps/web/app/api/telegram/route.ts | 170 | ||||
| -rw-r--r-- | apps/web/app/api/unfirlsite/route.ts | 292 | ||||
| -rw-r--r-- | apps/web/app/api/upload_image/route.ts | 92 |
14 files changed, 730 insertions, 715 deletions
diff --git a/apps/web/app/api/canvas/route.ts b/apps/web/app/api/canvas/route.ts index 70abace4..151478dd 100644 --- a/apps/web/app/api/canvas/route.ts +++ b/apps/web/app/api/canvas/route.ts @@ -1,10 +1,10 @@ export function GET(req: Request) { - const id = new URL(req.url).searchParams.get("id"); - return new Response(JSON.stringify(id)); + const id = new URL(req.url).searchParams.get("id"); + return new Response(JSON.stringify(id)); } export async function POST(req: Request) { - const body = await req.json(); - const id = new URL(req.url).searchParams.get("id"); - return new Response(JSON.stringify({ body, id })); + const body = await req.json(); + const id = new URL(req.url).searchParams.get("id"); + return new Response(JSON.stringify({ body, id })); } diff --git a/apps/web/app/api/canvasai/route.ts b/apps/web/app/api/canvasai/route.ts index 7e31f5b3..ac1baf03 100644 --- a/apps/web/app/api/canvasai/route.ts +++ b/apps/web/app/api/canvasai/route.ts @@ -4,28 +4,28 @@ import { ensureAuth } from "../ensureAuth"; export const runtime = "edge"; export async function POST(request: NextRequest) { - const session = await ensureAuth(request); - if (!session) { - return new Response("Unauthorized", { status: 401 }); - } - const res: { query: string } = await request.json(); + const session = await ensureAuth(request); + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + const res: { query: string } = await request.json(); - try { - const resp = await fetch( - `${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`, - ); - 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 }, - ); - } - return new Response( - JSON.stringify({ response: await resp.json(), status: 200 }), - ); - } catch (error) { - return new Response(`Error, ${error}`); - } + try { + const resp = await fetch( + `${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`, + ); + 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 }, + ); + } + return new Response( + JSON.stringify({ response: await resp.json(), status: 200 }), + ); + } catch (error) { + return new Response(`Error, ${error}`); + } } diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index 693b1be5..004bfd3b 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -1,9 +1,9 @@ import { type NextRequest } from "next/server"; import { - ChatHistory, - ChatHistoryZod, - convertChatHistoryList, - SourcesFromApi, + ChatHistory, + ChatHistoryZod, + convertChatHistoryList, + SourcesFromApi, } from "@repo/shared-types"; import { ensureAuth } from "../ensureAuth"; import { z } from "zod"; @@ -11,77 +11,77 @@ import { z } from "zod"; export const runtime = "edge"; export async function POST(req: NextRequest) { - const session = await ensureAuth(req); - - if (!session) { - return new Response("Unauthorized", { status: 401 }); - } - - if (!process.env.BACKEND_SECURITY_KEY) { - return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 }); - } - - const url = new URL(req.url); - - const query = url.searchParams.get("q"); - const spaces = url.searchParams.get("spaces"); - - const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false"; - - const jsonRequest = (await req.json()) as { - chatHistory: ChatHistory[]; - sources: SourcesFromApi[] | undefined; - }; - const { chatHistory, sources } = jsonRequest; - - if (!query || query.trim.length < 0) { - return new Response(JSON.stringify({ message: "Invalid query" }), { - status: 400, - }); - } - - const validated = 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); - - 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, - sources, - }), - }, - ); - - if (sourcesOnly == "true") { - const data = (await resp.json()) as SourcesFromApi; - 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 }, - ); - } - - return resp + const session = await ensureAuth(req); + + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + + if (!process.env.BACKEND_SECURITY_KEY) { + return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 }); + } + + const url = new URL(req.url); + + const query = url.searchParams.get("q"); + const spaces = url.searchParams.get("spaces"); + + const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false"; + + const jsonRequest = (await req.json()) as { + chatHistory: ChatHistory[]; + sources: SourcesFromApi[] | undefined; + }; + const { chatHistory, sources } = jsonRequest; + + if (!query || query.trim.length < 0) { + return new Response(JSON.stringify({ message: "Invalid query" }), { + status: 400, + }); + } + + const validated = 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); + + 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, + sources, + }), + }, + ); + + if (sourcesOnly == "true") { + const data = (await resp.json()) as SourcesFromApi; + 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 }, + ); + } + + return resp; } diff --git a/apps/web/app/api/editorai/route.ts b/apps/web/app/api/editorai/route.ts index 5e1fbf0c..ea580b3d 100644 --- a/apps/web/app/api/editorai/route.ts +++ b/apps/web/app/api/editorai/route.ts @@ -6,25 +6,27 @@ export const runtime = "edge"; // ERROR #2 - This the the next function that calls the backend, I sometimes think this is redundency, but whatever // I have commented the auth code, It should not work in development, but it still does sometimes 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() + // const d = await ensureAuth(request); + // if (!d) { + // return new Response("Unauthorized", { status: 401 }); + // } + const res: { context: string; request: string } = await request.json(); - try { - const resp = await fetch(`${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`); - // this just checks if there are erros I am keeping it commented for you to better understand the important pieces - // 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 }, - // ); - // } - return new Response(resp.body, { status: 200 }); - } catch (error) { - return new Response(`Error, ${error}`) - } -}
\ No newline at end of file + try { + const resp = await fetch( + `${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`, + ); + // this just checks if there are erros I am keeping it commented for you to better understand the important pieces + // 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 }, + // ); + // } + return new Response(resp.body, { status: 200 }); + } catch (error) { + return new Response(`Error, ${error}`); + } +} diff --git a/apps/web/app/api/ensureAuth.ts b/apps/web/app/api/ensureAuth.ts index d2c14b3b..a8c43cdc 100644 --- a/apps/web/app/api/ensureAuth.ts +++ b/apps/web/app/api/ensureAuth.ts @@ -4,30 +4,30 @@ import { sessions, users } from "../../server/db/schema"; import { eq } from "drizzle-orm"; export async function ensureAuth(req: NextRequest) { - // A helper function to protect routes + // A helper function to protect routes - const token = - req.cookies.get("next-auth.session-token")?.value ?? - req.cookies.get("__Secure-authjs.session-token")?.value ?? - req.cookies.get("authjs.session-token")?.value ?? - req.headers.get("Authorization")?.replace("Bearer ", ""); + const token = + req.cookies.get("next-auth.session-token")?.value ?? + req.cookies.get("__Secure-authjs.session-token")?.value ?? + req.cookies.get("authjs.session-token")?.value ?? + req.headers.get("Authorization")?.replace("Bearer ", ""); - if (!token) { - return undefined; - } + if (!token) { + return undefined; + } - const sessionData = await db - .select() - .from(sessions) - .innerJoin(users, eq(users.id, sessions.userId)) - .where(eq(sessions.sessionToken, token!)); + const sessionData = await db + .select() + .from(sessions) + .innerJoin(users, eq(users.id, sessions.userId)) + .where(eq(sessions.sessionToken, token!)); - if (!sessionData || sessionData.length === 0) { - return undefined; - } + if (!sessionData || sessionData.length === 0) { + return undefined; + } - return { - user: sessionData[0]!.user, - session: sessionData[0]!, - }; + return { + user: sessionData[0]!.user, + session: sessionData[0]!, + }; } diff --git a/apps/web/app/api/getCount/route.ts b/apps/web/app/api/getCount/route.ts index 7cd2a2d3..f91b7b94 100644 --- a/apps/web/app/api/getCount/route.ts +++ b/apps/web/app/api/getCount/route.ts @@ -7,41 +7,41 @@ import { ensureAuth } from "../ensureAuth"; export const runtime = "edge"; export async function GET(req: NextRequest) { - const session = await ensureAuth(req); + const session = await ensureAuth(req); - if (!session) { - return new Response("Unauthorized", { status: 401 }); - } + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } - const tweetsCount = await db - .select({ - count: sql<number>`count(*)`.mapWith(Number), - }) - .from(storedContent) - .where( - and( - eq(storedContent.userId, session.user.id), - eq(storedContent.type, "twitter-bookmark"), - ), - ); + const tweetsCount = await db + .select({ + count: sql<number>`count(*)`.mapWith(Number), + }) + .from(storedContent) + .where( + and( + eq(storedContent.userId, session.user.id), + eq(storedContent.type, "twitter-bookmark"), + ), + ); - const pageCount = await db - .select({ - count: sql<number>`count(*)`.mapWith(Number), - }) - .from(storedContent) - .where( - and( - eq(storedContent.userId, session.user.id), - ne(storedContent.type, "twitter-bookmark"), - ), - ); + const pageCount = await db + .select({ + count: sql<number>`count(*)`.mapWith(Number), + }) + .from(storedContent) + .where( + and( + eq(storedContent.userId, session.user.id), + ne(storedContent.type, "twitter-bookmark"), + ), + ); - return NextResponse.json({ - tweetsCount: tweetsCount[0]!.count, - tweetsLimit: 1000, - pageCount: pageCount[0]!.count, - pageLimit: 100, - user: session.user.email, - }); + return NextResponse.json({ + tweetsCount: tweetsCount[0]!.count, + tweetsLimit: 1000, + pageCount: pageCount[0]!.count, + pageLimit: 100, + user: session.user.email, + }); } diff --git a/apps/web/app/api/hello/route.ts b/apps/web/app/api/hello/route.ts index 363d0704..259d34fc 100644 --- a/apps/web/app/api/hello/route.ts +++ b/apps/web/app/api/hello/route.ts @@ -4,19 +4,19 @@ import { getRequestContext } from "@cloudflare/next-on-pages"; export const runtime = "edge"; export async function GET(request: NextRequest) { - let responseText = "Hello World"; + let responseText = "Hello World"; - // In the edge runtime you can use Bindings that are available in your application - // (for more details see: - // - https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application - // - https://developers.cloudflare.com/pages/functions/bindings/ - // ) - // - // KV Example: - // const myKv = getRequestContext().env.MY_KV_NAMESPACE - // await myKv.put('suffix', ' from a KV store!') - // const suffix = await myKv.get('suffix') - // responseText += suffix + // In the edge runtime you can use Bindings that are available in your application + // (for more details see: + // - https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application + // - https://developers.cloudflare.com/pages/functions/bindings/ + // ) + // + // KV Example: + // const myKv = getRequestContext().env.MY_KV_NAMESPACE + // await myKv.put('suffix', ' from a KV store!') + // const suffix = await myKv.get('suffix') + // responseText += suffix - return new Response(responseText); + return new Response(responseText); } diff --git a/apps/web/app/api/me/route.ts b/apps/web/app/api/me/route.ts index 621dcbfe..ab408f3e 100644 --- a/apps/web/app/api/me/route.ts +++ b/apps/web/app/api/me/route.ts @@ -6,42 +6,42 @@ import { type NextRequest, NextResponse } from "next/server"; export const runtime = "edge"; export async function GET(req: NextRequest) { - const token = - req.cookies.get("next-auth.session-token")?.value ?? - req.cookies.get("__Secure-authjs.session-token")?.value ?? - req.cookies.get("authjs.session-token")?.value ?? - req.headers.get("Authorization")?.replace("Bearer ", ""); + const token = + req.cookies.get("next-auth.session-token")?.value ?? + req.cookies.get("__Secure-authjs.session-token")?.value ?? + req.cookies.get("authjs.session-token")?.value ?? + req.headers.get("Authorization")?.replace("Bearer ", ""); - const session = await db - .select() - .from(sessions) - .where(eq(sessions.sessionToken, token!)); + const session = await db + .select() + .from(sessions) + .where(eq(sessions.sessionToken, token!)); - if (!session || session.length === 0) { - return new Response( - JSON.stringify({ message: "Invalid Key, session not found." }), - { status: 404 }, - ); - } + if (!session || session.length === 0) { + return new Response( + JSON.stringify({ message: "Invalid Key, session not found." }), + { status: 404 }, + ); + } - const user = await db - .select() - .from(users) - .where(eq(users.id, session[0]!.userId)) - .limit(1); + const user = await db + .select() + .from(users) + .where(eq(users.id, session[0]!.userId)) + .limit(1); - if (!user || user.length === 0) { - return NextResponse.json( - { message: "Invalid Key, session not found." }, - { status: 404 }, - ); - } + if (!user || user.length === 0) { + return NextResponse.json( + { message: "Invalid Key, session not found." }, + { status: 404 }, + ); + } - return new Response( - JSON.stringify({ - message: "OK", - data: { session: session[0], user: user[0] }, - }), - { status: 200 }, - ); + return new Response( + JSON.stringify({ + message: "OK", + data: { session: session[0], user: user[0] }, + }), + { status: 200 }, + ); } diff --git a/apps/web/app/api/mobile/newUser/route.ts b/apps/web/app/api/mobile/newUser/route.ts new file mode 100644 index 00000000..ed7cbdf3 --- /dev/null +++ b/apps/web/app/api/mobile/newUser/route.ts @@ -0,0 +1,13 @@ +import { NextRequest } from "next/server"; +import { z } from "zod"; + +export const runtime = "edge"; + +const newMobileUserBody = z.object({ + // this is a string in the format + encodedUserString: z.string(), +}); + +export async function POST(req: NextRequest) { + const body = await req.json(); +} diff --git a/apps/web/app/api/spaces/route.ts b/apps/web/app/api/spaces/route.ts index cbed547d..ae0215a1 100644 --- a/apps/web/app/api/spaces/route.ts +++ b/apps/web/app/api/spaces/route.ts @@ -7,23 +7,23 @@ import { ensureAuth } from "../ensureAuth"; export const runtime = "edge"; export async function GET(req: NextRequest) { - const session = await ensureAuth(req); + const session = await ensureAuth(req); - if (!session) { - return new Response("Unauthorized", { status: 401 }); - } + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } - const spaces = await db - .select() - .from(space) - .where(eq(space.user, session.user.id)) - .all(); + const spaces = await db + .select() + .from(space) + .where(eq(space.user, session.user.id)) + .all(); - return NextResponse.json( - { - message: "OK", - data: spaces, - }, - { status: 200 }, - ); + return NextResponse.json( + { + message: "OK", + data: spaces, + }, + { status: 200 }, + ); } diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts index 26636c5c..f9ab7c01 100644 --- a/apps/web/app/api/store/route.ts +++ b/apps/web/app/api/store/route.ts @@ -11,204 +11,204 @@ import { limit } from "@/app/actions/doers"; export const runtime = "edge"; const createMemoryFromAPI = async (input: { - data: AddFromAPIType; - userId: string; + data: AddFromAPIType; + userId: string; }) => { - if (!(await limit(input.userId, input.data.type))) { - return { - success: false, - data: 0, - error: `You have exceeded the limit of ${LIMITS[input.data.type as keyof typeof LIMITS]} ${input.data.type}s.`, - }; - } - - const vectorSaveResponse = await fetch( - `${process.env.BACKEND_BASE_URL}/api/add`, - { - method: "POST", - body: JSON.stringify({ - pageContent: input.data.pageContent, - title: input.data.title, - description: input.data.description, - url: input.data.url, - spaces: input.data.spaces, - user: input.userId, - type: input.data.type, - }), - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, - }, - }, - ); - - if (!vectorSaveResponse.ok) { - const errorData = await vectorSaveResponse.text(); - console.error(errorData); - return { - success: false, - data: 0, - error: `Failed to save to vector store. Backend returned error: ${errorData}`, - }; - } - - let contentId: number | undefined; - - const saveToDbUrl = - (input.data.url.split("#supermemory-user-")[0] ?? input.data.url) + - "#supermemory-user-" + - input.userId; - - const noteId = new Date().getTime(); - - // Insert into database - try { - const insertResponse = await db - .insert(storedContent) - .values({ - content: input.data.pageContent, - title: input.data.title, - description: input.data.description, - url: saveToDbUrl, - baseUrl: saveToDbUrl, - image: input.data.image, - savedAt: new Date(), - userId: input.userId, - type: input.data.type, - noteId, - }) - .returning({ id: storedContent.id }); - - contentId = insertResponse[0]?.id; - } catch (e) { - const error = e as Error; - console.log("Error: ", error.message); - - if (error.message.includes("D1_ERROR: UNIQUE constraint failed:")) { - return { - success: false, - data: 0, - error: "Content already exists", - }; - } - - return { - success: false, - data: 0, - error: "Failed to save to database with error: " + error.message, - }; - } - - if (!contentId) { - return { - success: false, - data: 0, - error: "Failed to save to database", - }; - } - - if (input.data.spaces.length > 0) { - // Adding the many-to-many relationship between content and spaces - const spaceData = await db - .select() - .from(space) - .where( - and( - inArray( - space.id, - input.data.spaces.map((s) => parseInt(s)), - ), - eq(space.user, input.userId), - ), - ) - .all(); - - await Promise.all( - spaceData.map(async (s) => { - await db - .insert(contentToSpace) - .values({ contentId: contentId, spaceId: s.id }); - - await db.update(space).set({ numItems: s.numItems + 1 }); - }), - ); - } - - try { - const response = await vectorSaveResponse.json(); - - const expectedResponse = z.object({ status: z.literal("ok") }); - - const parsedResponse = expectedResponse.safeParse(response); - - if (!parsedResponse.success) { - return { - success: false, - data: 0, - error: `Failed to save to vector store. Backend returned error: ${parsedResponse.error.message}`, - }; - } - - return { - success: true, - data: 1, - }; - } catch (e) { - return { - success: false, - data: 0, - error: `Failed to save to vector store. Backend returned error: ${e}`, - }; - } + if (!(await limit(input.userId, input.data.type))) { + return { + success: false, + data: 0, + error: `You have exceeded the limit of ${LIMITS[input.data.type as keyof typeof LIMITS]} ${input.data.type}s.`, + }; + } + + const vectorSaveResponse = await fetch( + `${process.env.BACKEND_BASE_URL}/api/add`, + { + method: "POST", + body: JSON.stringify({ + pageContent: input.data.pageContent, + title: input.data.title, + description: input.data.description, + url: input.data.url, + spaces: input.data.spaces, + user: input.userId, + type: input.data.type, + }), + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, + }, + }, + ); + + if (!vectorSaveResponse.ok) { + const errorData = await vectorSaveResponse.text(); + console.error(errorData); + return { + success: false, + data: 0, + error: `Failed to save to vector store. Backend returned error: ${errorData}`, + }; + } + + let contentId: number | undefined; + + const saveToDbUrl = + (input.data.url.split("#supermemory-user-")[0] ?? input.data.url) + + "#supermemory-user-" + + input.userId; + + const noteId = new Date().getTime(); + + // Insert into database + try { + const insertResponse = await db + .insert(storedContent) + .values({ + content: input.data.pageContent, + title: input.data.title, + description: input.data.description, + url: saveToDbUrl, + baseUrl: saveToDbUrl, + image: input.data.image, + savedAt: new Date(), + userId: input.userId, + type: input.data.type, + noteId, + }) + .returning({ id: storedContent.id }); + + contentId = insertResponse[0]?.id; + } catch (e) { + const error = e as Error; + console.log("Error: ", error.message); + + if (error.message.includes("D1_ERROR: UNIQUE constraint failed:")) { + return { + success: false, + data: 0, + error: "Content already exists", + }; + } + + return { + success: false, + data: 0, + error: "Failed to save to database with error: " + error.message, + }; + } + + if (!contentId) { + return { + success: false, + data: 0, + error: "Failed to save to database", + }; + } + + if (input.data.spaces.length > 0) { + // Adding the many-to-many relationship between content and spaces + const spaceData = await db + .select() + .from(space) + .where( + and( + inArray( + space.id, + input.data.spaces.map((s) => parseInt(s)), + ), + eq(space.user, input.userId), + ), + ) + .all(); + + await Promise.all( + spaceData.map(async (s) => { + await db + .insert(contentToSpace) + .values({ contentId: contentId, spaceId: s.id }); + + await db.update(space).set({ numItems: s.numItems + 1 }); + }), + ); + } + + try { + const response = await vectorSaveResponse.json(); + + const expectedResponse = z.object({ status: z.literal("ok") }); + + const parsedResponse = expectedResponse.safeParse(response); + + if (!parsedResponse.success) { + return { + success: false, + data: 0, + error: `Failed to save to vector store. Backend returned error: ${parsedResponse.error.message}`, + }; + } + + return { + success: true, + data: 1, + }; + } catch (e) { + return { + success: false, + data: 0, + error: `Failed to save to vector store. Backend returned error: ${e}`, + }; + } }; export async function POST(req: NextRequest) { - const session = await ensureAuth(req); - - if (!session) { - return new Response("Unauthorized", { status: 401 }); - } - - if (!process.env.BACKEND_SECURITY_KEY) { - return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 }); - } - - const body = await req.json(); - - const validated = addFromAPIType.safeParse(body); - - if (!validated.success) { - return new Response( - JSON.stringify({ - message: "Invalid request", - error: validated.error, - }), - { status: 400 }, - ); - } - - const data = validated.data; - - const result = await createMemoryFromAPI({ - data, - userId: session.user.id, - }); - - if (!result.success) { - return new Response( - JSON.stringify({ - message: "Failed to save document", - error: result.error, - }), - { status: 501 }, - ); - } - - return new Response( - JSON.stringify({ - message: "Document saved", - data: result.data, - }), - { status: 200 }, - ); + const session = await ensureAuth(req); + + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + + if (!process.env.BACKEND_SECURITY_KEY) { + return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 }); + } + + const body = await req.json(); + + const validated = addFromAPIType.safeParse(body); + + if (!validated.success) { + return new Response( + JSON.stringify({ + message: "Invalid request", + error: validated.error, + }), + { status: 400 }, + ); + } + + const data = validated.data; + + const result = await createMemoryFromAPI({ + data, + userId: session.user.id, + }); + + if (!result.success) { + return new Response( + JSON.stringify({ + message: "Failed to save document", + error: result.error, + }), + { status: 501 }, + ); + } + + return new Response( + JSON.stringify({ + message: "Document saved", + data: result.data, + }), + { status: 200 }, + ); } diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts index 5f824b79..78837e5f 100644 --- a/apps/web/app/api/telegram/route.ts +++ b/apps/web/app/api/telegram/route.ts @@ -8,7 +8,7 @@ import { User } from "grammy/types"; export const runtime = "edge"; if (!process.env.TELEGRAM_BOT_TOKEN) { - throw new Error("TELEGRAM_BOT_TOKEN is not defined"); + throw new Error("TELEGRAM_BOT_TOKEN is not defined"); } console.log("Telegram bot activated"); @@ -17,97 +17,97 @@ const token = process.env.TELEGRAM_BOT_TOKEN; const bot = new Bot(token); bot.command("start", async (ctx) => { - const user: User = (await ctx.getAuthor()).user; + const user: User = (await ctx.getAuthor()).user; - const cipherd = cipher(user.id.toString()); - await ctx.reply( - `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`, - ); + const cipherd = cipher(user.id.toString()); + await ctx.reply( + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`, + ); }); bot.on("message", async (ctx) => { - const user: User = (await ctx.getAuthor()).user; - - const cipherd = cipher(user.id.toString()); - - const dbUser = await db.query.users - .findFirst({ - where: eq(users.telegramId, user.id.toString()), - }) - .execute(); - - if (!dbUser) { - await ctx.reply( - `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`, - ); - - return; - } - - const message = await ctx.reply("I'm thinking..."); - - const response = await fetch( - `${process.env.BACKEND_BASE_URL}/api/autoChatOrAdd?query=${ctx.message.text}&user=${dbUser.id}`, - { - method: "POST", - headers: { - Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - // TODO: we can use the conversations API to get the last 5 messages - // get chatHistory from this conversation. - // Basically the last 5 messages between the user and the assistant. - // In ths form of [{role: 'user' | 'assistant', content: string}] - // https://grammy.dev/plugins/conversations - chatHistory: [], - }), - }, - ); - - if (response.status !== 200) { - console.log("Failed to get response from backend"); - console.log(response.status); - console.log(await response.text()); - await ctx.reply( - "Sorry, I am not able to process your request at the moment.", - ); - return; - } - - const data = (await response.json()) as { - status: string; - response: string; - contentAdded: { - type: string; - content: string; - url: string; - }; - }; - - // TODO: we might want to enrich this data with more information - if (data.contentAdded) { - await db - .insert(storedContent) - .values({ - content: data.contentAdded.content, - title: `${data.contentAdded.content.slice(0, 30)}... (Added from chatbot)`, - description: "", - url: data.contentAdded.url, - baseUrl: data.contentAdded.url, - image: "", - savedAt: new Date(), - userId: dbUser.id, - type: data.contentAdded.type, - }) - .returning({ id: storedContent.id }); - } - - await ctx.api.editMessageText(ctx.chat.id, message.message_id, data.response); + const user: User = (await ctx.getAuthor()).user; + + const cipherd = cipher(user.id.toString()); + + const dbUser = await db.query.users + .findFirst({ + where: eq(users.telegramId, user.id.toString()), + }) + .execute(); + + if (!dbUser) { + await ctx.reply( + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://supermemory.ai/signin?telegramUser=${cipherd}`, + ); + + return; + } + + const message = await ctx.reply("I'm thinking..."); + + const response = await fetch( + `${process.env.BACKEND_BASE_URL}/api/autoChatOrAdd?query=${ctx.message.text}&user=${dbUser.id}`, + { + method: "POST", + headers: { + Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + // TODO: we can use the conversations API to get the last 5 messages + // get chatHistory from this conversation. + // Basically the last 5 messages between the user and the assistant. + // In ths form of [{role: 'user' | 'assistant', content: string}] + // https://grammy.dev/plugins/conversations + chatHistory: [], + }), + }, + ); + + if (response.status !== 200) { + console.log("Failed to get response from backend"); + console.log(response.status); + console.log(await response.text()); + await ctx.reply( + "Sorry, I am not able to process your request at the moment.", + ); + return; + } + + const data = (await response.json()) as { + status: string; + response: string; + contentAdded: { + type: string; + content: string; + url: string; + }; + }; + + // TODO: we might want to enrich this data with more information + if (data.contentAdded) { + await db + .insert(storedContent) + .values({ + content: data.contentAdded.content, + title: `${data.contentAdded.content.slice(0, 30)}... (Added from chatbot)`, + description: "", + url: data.contentAdded.url, + baseUrl: data.contentAdded.url, + image: "", + savedAt: new Date(), + userId: dbUser.id, + type: data.contentAdded.type, + }) + .returning({ id: storedContent.id }); + } + + await ctx.api.editMessageText(ctx.chat.id, message.message_id, data.response); }); export const POST = webhookCallback(bot, "std/http"); export const GET = async () => { - return new Response("OK", { status: 200 }); + return new Response("OK", { status: 200 }); }; diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts index 36e47987..78b61ca9 100644 --- a/apps/web/app/api/unfirlsite/route.ts +++ b/apps/web/app/api/unfirlsite/route.ts @@ -7,150 +7,150 @@ import { ensureAuth } from "../ensureAuth"; export const runtime = "edge"; export async function POST(request: NextRequest) { - const r2 = new AwsClient({ - accessKeyId: process.env.R2_ACCESS_KEY_ID, - secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, - }); - - 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 } = {}; - - $("meta[property^=og:]").each( - // @ts-ignore, it just works so why care of type safety if someone has better way go ahead - (_, el) => (og[$(el).attr("property")!] = $(el).attr("content")), - ); - $("meta[name^=twitter:]").each( - // @ts-ignore - (_, 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, - }; - } - - 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)); - } - - if (!process.env.DEV_IMAGES) { - return new Response("Missing DEV_IMAGES namespace.", { status: 500 }); - } - - 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, - }), - ); - } + const r2 = new AwsClient({ + accessKeyId: process.env.R2_ACCESS_KEY_ID, + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, + }); + + 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 } = {}; + + $("meta[property^=og:]").each( + // @ts-ignore, it just works so why care of type safety if someone has better way go ahead + (_, el) => (og[$(el).attr("property")!] = $(el).attr("content")), + ); + $("meta[name^=twitter:]").each( + // @ts-ignore + (_, 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, + }; + } + + 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)); + } + + if (!process.env.DEV_IMAGES) { + return new Response("Missing DEV_IMAGES namespace.", { status: 500 }); + } + + 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, + }), + ); + } } diff --git a/apps/web/app/api/upload_image/route.ts b/apps/web/app/api/upload_image/route.ts index 0d93c5b0..7ba75576 100644 --- a/apps/web/app/api/upload_image/route.ts +++ b/apps/web/app/api/upload_image/route.ts @@ -7,50 +7,50 @@ import { ensureAuth } from "../ensureAuth"; export const runtime = "edge"; export async function PUT(request: NextRequest) { - const d = await ensureAuth(request); - - if (!d) { - return new Response("Unauthorized", { status: 401 }); - } - - const reqUrl = new URL(request.url); - const filename = reqUrl.searchParams.get("filename"); - - if (!filename) { - return new Response("Missing filename", { status: 400 }); - } - - if ( - !process.env.R2_ENDPOINT || - !process.env.R2_ACCESS_ID || - !process.env.R2_SECRET_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 s3 = new S3Client({ - region: "auto", - endpoint: process.env.R2_ENDPOINT, - credentials: { - accessKeyId: process.env.R2_ACCESS_ID, - secretAccessKey: process.env.R2_SECRET_KEY, - }, - }); - - const url = await getSignedUrl( - s3, - new PutObjectCommand({ Bucket: process.env.R2_BUCKET_NAME, Key: filename }), - { expiresIn: 3600 }, - ); - - return new Response(JSON.stringify({ url }), { - status: 200, - headers: { - "Content-Type": "application/json", - }, - }); + const d = await ensureAuth(request); + + if (!d) { + return new Response("Unauthorized", { status: 401 }); + } + + const reqUrl = new URL(request.url); + const filename = reqUrl.searchParams.get("filename"); + + if (!filename) { + return new Response("Missing filename", { status: 400 }); + } + + if ( + !process.env.R2_ENDPOINT || + !process.env.R2_ACCESS_ID || + !process.env.R2_SECRET_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 s3 = new S3Client({ + region: "auto", + endpoint: process.env.R2_ENDPOINT, + credentials: { + accessKeyId: process.env.R2_ACCESS_ID, + secretAccessKey: process.env.R2_SECRET_KEY, + }, + }); + + const url = await getSignedUrl( + s3, + new PutObjectCommand({ Bucket: process.env.R2_BUCKET_NAME, Key: filename }), + { expiresIn: 3600 }, + ); + + return new Response(JSON.stringify({ url }), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); } |