aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/api
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-07-16 19:26:47 -0500
committerDhravya <[email protected]>2024-07-16 19:26:47 -0500
commit5b44dcb1d1f7fcea331b248800cad60129038eab (patch)
treec5562fd33a95d09e625fea7586392eacfcc3fca7 /apps/web/app/api
parentbreadcrumbs (diff)
downloadarchived-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.ts10
-rw-r--r--apps/web/app/api/canvasai/route.ts46
-rw-r--r--apps/web/app/api/chat/route.ts154
-rw-r--r--apps/web/app/api/editorai/route.ts44
-rw-r--r--apps/web/app/api/ensureAuth.ts42
-rw-r--r--apps/web/app/api/getCount/route.ts66
-rw-r--r--apps/web/app/api/hello/route.ts26
-rw-r--r--apps/web/app/api/me/route.ts66
-rw-r--r--apps/web/app/api/mobile/newUser/route.ts13
-rw-r--r--apps/web/app/api/spaces/route.ts32
-rw-r--r--apps/web/app/api/store/route.ts392
-rw-r--r--apps/web/app/api/telegram/route.ts170
-rw-r--r--apps/web/app/api/unfirlsite/route.ts292
-rw-r--r--apps/web/app/api/upload_image/route.ts92
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",
+ },
+ });
}