mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
210 lines
5 KiB
TypeScript
210 lines
5 KiB
TypeScript
import { type NextRequest } from "next/server";
|
|
import { addFromAPIType, AddFromAPIType } from "@repo/shared-types";
|
|
import { ensureAuth } from "../ensureAuth";
|
|
import { z } from "zod";
|
|
import { db } from "@/server/db";
|
|
import { contentToSpace, space, storedContent } from "@/server/db/schema";
|
|
import { and, eq, inArray } from "drizzle-orm";
|
|
import { LIMITS } from "@/lib/constants";
|
|
import { limit } from "@/app/actions/doers";
|
|
|
|
export const runtime = "edge";
|
|
|
|
const createMemoryFromAPI = async (input: {
|
|
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: storedContent.baseUrl",
|
|
)
|
|
) {
|
|
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 (space) => {
|
|
await db
|
|
.insert(contentToSpace)
|
|
.values({ contentId: contentId, spaceId: space.id });
|
|
}),
|
|
);
|
|
}
|
|
|
|
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: 500 },
|
|
);
|
|
}
|
|
|
|
return new Response("ok", { status: 200 });
|
|
}
|