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