aboutsummaryrefslogtreecommitdiff
path: root/apps/backend/src
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2025-03-03 10:59:34 -0600
committerDhravya Shah <[email protected]>2025-03-03 10:59:34 -0600
commitff14d77c11153b4679967b6aa26600a2b7a0e2a0 (patch)
tree3936616ec2341117ebbfc237d4c1fcef2369694e /apps/backend/src
parentMerge pull request #338 from supermemoryai/Dhravya-patch-2 (diff)
downloadsupermemory-ff14d77c11153b4679967b6aa26600a2b7a0e2a0.tar.xz
supermemory-ff14d77c11153b4679967b6aa26600a2b7a0e2a0.zip
auto generated openapi schema
Diffstat (limited to 'apps/backend/src')
-rw-r--r--apps/backend/src/index.tsx377
-rw-r--r--apps/backend/src/minimal.ts18
-rw-r--r--apps/backend/src/routes/actions.ts6
-rw-r--r--apps/backend/src/routes/integrations.ts321
-rw-r--r--apps/backend/src/routes/memories.ts5
-rw-r--r--apps/backend/src/routes/spaces.ts10
-rw-r--r--apps/backend/src/routes/user.ts42
7 files changed, 414 insertions, 365 deletions
diff --git a/apps/backend/src/index.tsx b/apps/backend/src/index.tsx
index 48790e90..1e61304d 100644
--- a/apps/backend/src/index.tsx
+++ b/apps/backend/src/index.tsx
@@ -17,6 +17,7 @@ import spacesRoute from "./routes/spaces";
import actions from "./routes/actions";
import memories from "./routes/memories";
import integrations from "./routes/integrations";
+import { fromHono, OpenAPIRoute } from "chanfana";
import {
cloudflareRateLimiter,
DurableObjectRateLimiter,
@@ -24,190 +25,203 @@ import {
} from "@hono-rate-limiter/cloudflare";
import { ConfigType, GeneralConfigType, rateLimiter } from "hono-rate-limiter";
-export const app = new Hono<{ Variables: Variables; Bindings: Env }>()
- .use("*", timing())
- .use("*", logger())
- .use(
- "*",
- cors({
- origin: [
- "http://localhost:3000",
- "https://supermemory.ai",
- "https://*.supermemory.ai",
- "https://*.supermemory.com",
- "https://supermemory.com",
- "chrome-extension://*",
- ],
- allowHeaders: ["*"],
- allowMethods: ["*"],
- credentials: true,
- exposeHeaders: ["*"],
- })
- )
- .use("/v1/*", auth)
- .use("/v1/*", (c, next) => {
- const user = c.get("user");
-
- if (c.env.NODE_ENV === "development") {
- return next();
- }
+// Create base Hono app first
+const honoApp = new Hono<{ Variables: Variables; Bindings: Env }>();
+
+const app = fromHono(honoApp, {
+ base: "",
+});
+
+// Add all middleware and routes
+app.use("*", timing());
+app.use("*", logger());
+app.use(
+ "*",
+ cors({
+ origin: [
+ "http://localhost:3000",
+ "https://supermemory.ai",
+ "https://*.supermemory.ai",
+ "https://*.supermemory.com",
+ "https://supermemory.com",
+ "chrome-extension://*",
+ ],
+ allowHeaders: ["*"],
+ allowMethods: ["*"],
+ credentials: true,
+ exposeHeaders: ["*"],
+ })
+);
+
+app.use("/v1/*", auth);
+app.use("/v1/*", (c, next) => {
+ const user = c.get("user");
- // RATELIMITS
- const rateLimitConfig = {
- // Endpoints that bypass rate limiting
- excludedPaths: [
- "/v1/add",
- "/v1/chat",
- "/v1/suggested-learnings",
- "/v1/recommended-questions",
- ] as (string | RegExp)[],
-
- // Custom rate limits for specific endpoints
- customLimits: {
- notionImport: {
- paths: ["/v1/integrations/notion/import", "/v1/integrations/notion"],
- windowMs: 10 * 60 * 1000, // 10 minutes
- limit: 5, // 5 requests per 10 minutes
- },
- inviteSpace: {
- paths: [/^\v1\/spaces\/[^/]+\/invite$/],
- windowMs: 60 * 1000, // 1 minute
- limit: 5, // 5 requests per minute
- },
- } as Record<
- string,
- { paths: (string | RegExp)[]; windowMs: number; limit: number }
- >,
-
- default: {
+ if (c.env.NODE_ENV === "development") {
+ return next();
+ }
+
+ // RATELIMITS
+ const rateLimitConfig = {
+ // Endpoints that bypass rate limiting
+ excludedPaths: [
+ "/v1/add",
+ "/v1/chat",
+ "/v1/suggested-learnings",
+ "/v1/recommended-questions",
+ ] as (string | RegExp)[],
+
+ // Custom rate limits for specific endpoints
+ customLimits: {
+ notionImport: {
+ paths: ["/v1/integrations/notion/import", "/v1/integrations/notion"],
+ windowMs: 10 * 60 * 1000, // 10 minutes
+ limit: 5, // 5 requests per 10 minutes
+ },
+ inviteSpace: {
+ paths: [/^\v1\/spaces\/[^/]+\/invite$/],
windowMs: 60 * 1000, // 1 minute
- limit: 100, // 100 requests per minute
+ limit: 5, // 5 requests per minute
},
+ } as Record<
+ string,
+ { paths: (string | RegExp)[]; windowMs: number; limit: number }
+ >,
+
+ default: {
+ windowMs: 60 * 1000, // 1 minute
+ limit: 100, // 100 requests per minute
+ },
- common: {
- standardHeaders: "draft-6",
- keyGenerator: (c: Context) =>
- (user?.uuid ?? c.req.header("cf-connecting-ip")) +
- "-" +
- new Date().getDate(), // day so that limit gets reset every day
- store: new DurableObjectStore({ namespace: c.env.RATE_LIMITER }),
- } as GeneralConfigType<ConfigType>,
- };
+ common: {
+ standardHeaders: "draft-6",
+ keyGenerator: (c: Context) =>
+ (user?.uuid ?? c.req.header("cf-connecting-ip")) +
+ "-" +
+ new Date().getDate(), // day so that limit gets reset every day
+ store: new DurableObjectStore({ namespace: c.env.RATE_LIMITER }),
+ } as GeneralConfigType<ConfigType>,
+ };
+ if (
+ c.req.path &&
+ rateLimitConfig.excludedPaths.some((path) =>
+ typeof path === "string" ? c.req.path === path : path.test(c.req.path)
+ )
+ ) {
+ return next();
+ }
+
+ // Check for custom rate limits
+ for (const [_, config] of Object.entries(rateLimitConfig.customLimits)) {
if (
- c.req.path &&
- rateLimitConfig.excludedPaths.some((path) =>
+ config.paths.some((path) =>
typeof path === "string" ? c.req.path === path : path.test(c.req.path)
)
) {
- return next();
+ return rateLimiter({
+ windowMs: config.windowMs,
+ limit: config.limit,
+ ...rateLimitConfig.common,
+ })(c as any, next);
}
+ }
- // Check for custom rate limits
- for (const [_, config] of Object.entries(rateLimitConfig.customLimits)) {
- if (
- config.paths.some((path) =>
- typeof path === "string" ? c.req.path === path : path.test(c.req.path)
- )
- ) {
- return rateLimiter({
- windowMs: config.windowMs,
- limit: config.limit,
- ...rateLimitConfig.common,
- })(c as any, next);
- }
- }
+ // Apply default rate limit
+ return rateLimiter({
+ windowMs: rateLimitConfig.default.windowMs,
+ limit: rateLimitConfig.default.limit,
+ ...rateLimitConfig.common,
+ })(c as any, next);
+});
- // Apply default rate limit
- return rateLimiter({
- windowMs: rateLimitConfig.default.windowMs,
- limit: rateLimitConfig.default.limit,
- ...rateLimitConfig.common,
- })(c as any, next);
- })
- .get("/", (c) => {
- return c.html(<LandingPage />);
- })
- // TEMPORARY REDIRECT
- .all("/api/*", async (c) => {
- // Get the full URL and path
- const url = new URL(c.req.url);
- const path = url.pathname;
- const newPath = path.replace("/api", "/v1");
-
- // Preserve query parameters and build target URL
- const redirectUrl = "https://api.supermemory.ai" + newPath + url.search;
-
- // Use c.redirect() for a proper redirect
- return c.redirect(redirectUrl);
- })
- .route("/v1/user", user)
- .route("/v1/spaces", spacesRoute)
- .route("/v1", actions)
- .route("/v1/integrations", integrations)
- .route("/v1/memories", memories)
- .get("/v1/session", (c) => {
- const user = c.get("user");
-
- if (!user) {
- return c.json({ error: "Unauthorized" }, 401);
- }
+app.get("/", (c) => {
+ return c.html(<LandingPage />);
+});
+
+// TEMPORARY REDIRECT
+app.all("/api/*", async (c) => {
+ // Get the full URL and path
+ const url = new URL(c.req.url);
+ const path = url.pathname;
+ const newPath = path.replace("/api", "/v1");
+
+ // Preserve query parameters and build target URL
+ const redirectUrl = "https://api.supermemory.ai" + newPath + url.search;
- return c.json({
- user,
+ // Use c.redirect() for a proper redirect
+ return c.redirect(redirectUrl);
+});
+
+app.route("/v1/user", user);
+app.route("/v1/spaces", spacesRoute);
+app.route("/v1", actions);
+app.route("/v1/integrations", integrations);
+app.route("/v1/memories", memories);
+
+app.get("/v1/session", (c) => {
+ const user = c.get("user");
+
+ if (!user) {
+ return c.json({ error: "Unauthorized" }, 401);
+ }
+
+ return c.json({
+ user,
+ });
+});
+
+app.post(
+ "/waitlist",
+ zValidator(
+ "json",
+ z.object({ email: z.string().email(), token: z.string() })
+ ),
+ async (c) => {
+ const { email, token } = c.req.valid("json");
+
+ const address = c.req.raw.headers.get("CF-Connecting-IP");
+
+ const idempotencyKey = crypto.randomUUID();
+ const url = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
+ const firstResult = await fetch(url, {
+ body: JSON.stringify({
+ secret: c.env.TURNSTILE_SECRET_KEY,
+ response: token,
+ remoteip: address,
+ idempotency_key: idempotencyKey,
+ }),
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
});
- })
- .post(
- "/waitlist",
- zValidator(
- "json",
- z.object({ email: z.string().email(), token: z.string() })
- ),
- async (c) => {
- const { email, token } = c.req.valid("json");
-
- const address = c.req.raw.headers.get("CF-Connecting-IP");
-
- const idempotencyKey = crypto.randomUUID();
- const url = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
- const firstResult = await fetch(url, {
- body: JSON.stringify({
- secret: c.env.TURNSTILE_SECRET_KEY,
- response: token,
- remoteip: address,
- idempotency_key: idempotencyKey,
- }),
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- });
- const firstOutcome = (await firstResult.json()) as { success: boolean };
+ const firstOutcome = (await firstResult.json()) as { success: boolean };
- if (!firstOutcome.success) {
- console.info("Turnstile verification failed", firstOutcome);
- return c.json(
- { error: "Turnstile verification failed" },
- 439 as StatusCode
- );
- }
+ if (!firstOutcome.success) {
+ console.info("Turnstile verification failed", firstOutcome);
+ return c.json(
+ { error: "Turnstile verification failed" },
+ 439 as StatusCode
+ );
+ }
- const resend = new Resend(c.env.RESEND_API_KEY);
+ const resend = new Resend(c.env.RESEND_API_KEY);
- const db = database(c.env.HYPERDRIVE.connectionString);
+ const db = database(c.env.HYPERDRIVE.connectionString);
- const ip =
- c.req.header("cf-connecting-ip") ||
- `${c.req.raw.cf?.asn}-${c.req.raw.cf?.country}-${c.req.raw.cf?.city}-${c.req.raw.cf?.region}-${c.req.raw.cf?.postalCode}`;
+ const ip =
+ c.req.header("cf-connecting-ip") ||
+ `${c.req.raw.cf?.asn}-${c.req.raw.cf?.country}-${c.req.raw.cf?.city}-${c.req.raw.cf?.region}-${c.req.raw.cf?.postalCode}`;
- const { success } = await c.env.EMAIL_LIMITER.limit({ key: ip });
+ const { success } = await c.env.EMAIL_LIMITER.limit({ key: ip });
- if (!success) {
- return c.json({ error: "Rate limit exceeded" }, 429);
- }
+ if (!success) {
+ return c.json({ error: "Rate limit exceeded" }, 429);
+ }
- const message = `Supermemory started as a side project a few months ago when I built it as a hackathon project.
+ const message = `Supermemory started as a side project a few months ago when I built it as a hackathon project.
<br></br>
you guys loved it too much. like wayy too much. it was embarrassing, because this was not it - it was nothing but a hackathon project.
<br></br>
@@ -216,26 +230,27 @@ export const app = new Hono<{ Variables: Variables; Bindings: Env }>()
So, it's time to make this good. My vision is to make supermemory the best memory tool on the internet.
`;
- try {
- await db.insert(waitlist).values({ email });
- await resend.emails.send({
- from: "Dhravya From Supermemory <[email protected]>",
- to: email,
- subject: "You're in the waitlist - A personal note from Dhravya",
- html: `<p>Hi. I'm Dhravya. I'm building Supermemory to help people remember everything.<br></br> ${message} <br></br><br></br>I'll be in touch when we launch! Till then, just reply to this email if you wanna talk :)<br></br>If you want to follow me on X, here's my handle: <a href='https://x.com/dhravyashah'>@dhravyashah</a><br></br><br></br>- Dhravya</p>`,
- });
- } catch (e) {
- console.error(e);
- return c.json({ error: "Failed to add to waitlist" }, 400);
- }
-
- return c.json({ success: true });
+ try {
+ await db.insert(waitlist).values({ email });
+ await resend.emails.send({
+ from: "Dhravya From Supermemory <[email protected]>",
+ to: email,
+ subject: "You're in the waitlist - A personal note from Dhravya",
+ html: `<p>Hi. I'm Dhravya. I'm building Supermemory to help people remember everything.<br></br> ${message} <br></br><br></br>I'll be in touch when we launch! Till then, just reply to this email if you wanna talk :)<br></br>If you want to follow me on X, here's my handle: <a href='https://x.com/dhravyashah'>@dhravyashah</a><br></br><br></br>- Dhravya</p>`,
+ });
+ } catch (e) {
+ console.error(e);
+ return c.json({ error: "Failed to add to waitlist" }, 400);
}
- )
- .onError((err, c) => {
- console.error(err);
- return c.json({ error: "Internal server error" }, 500);
- });
+
+ return c.json({ success: true });
+ }
+);
+
+app.onError((err, c) => {
+ console.error(err);
+ return c.json({ error: "Internal server error" }, 500);
+});
export default {
fetch: app.fetch,
diff --git a/apps/backend/src/minimal.ts b/apps/backend/src/minimal.ts
new file mode 100644
index 00000000..4647dd33
--- /dev/null
+++ b/apps/backend/src/minimal.ts
@@ -0,0 +1,18 @@
+import { fromHono } from "chanfana";
+import { Hono } from "hono";
+import { serve as serveHono } from "@hono/node-server";
+
+const app = fromHono(new Hono());
+
+app.get("/", (c) => {
+ return c.json({ message: "Hello, world!" });
+});
+
+app.get("/entry/:id", (c) => {
+ const id = c.req.param("id");
+ return c.json({ message: `Hello, ${id}!` });
+});
+
+serveHono(app, (info) => {
+ console.log(info);
+});
diff --git a/apps/backend/src/routes/actions.ts b/apps/backend/src/routes/actions.ts
index 37cc18e4..f26b46a5 100644
--- a/apps/backend/src/routes/actions.ts
+++ b/apps/backend/src/routes/actions.ts
@@ -38,8 +38,11 @@ import {
} from "@supermemory/db";
import { typeDecider } from "../utils/typeDecider";
import { isErr, Ok } from "../errors/results";
+import { fromHono } from "chanfana";
-const actions = new Hono<{ Variables: Variables; Bindings: Env }>()
+const actions = fromHono(new Hono<{ Variables: Variables; Bindings: Env }>(), {
+ base: "",
+})
.post(
"/chat",
zValidator(
@@ -1195,3 +1198,4 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>()
);
export default actions;
+import { Context } from 'hono/jsx'
diff --git a/apps/backend/src/routes/integrations.ts b/apps/backend/src/routes/integrations.ts
index a4ba98f7..a76563d7 100644
--- a/apps/backend/src/routes/integrations.ts
+++ b/apps/backend/src/routes/integrations.ts
@@ -5,173 +5,180 @@ import { getAllNotionPageContents } from "../utils/notion";
import { and, eq, or } from "@supermemory/db";
import { documents } from "@supermemory/db/schema";
import { database } from "@supermemory/db";
+import { fromHono } from "chanfana";
+
+const integrations = fromHono(
+ new Hono<{ Variables: Variables; Bindings: Env }>(),
+ {
+ base: "",
+ }
+).get("/notion/import", async (c) => {
+ const user = c.get("user");
+ if (!user) {
+ return c.json({ error: "Unauthorized" }, 401);
+ }
+
+ // Create SSE stream
+ const stream = new TransformStream();
+ const writer = stream.writable.getWriter();
+ const encoder = new TextEncoder();
+
+ // Create response first so client gets headers immediately
+ const response = new Response(stream.readable, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ // Required CORS headers for SSE
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Credentials": "true",
+ },
+ });
-const integrations = new Hono<{ Variables: Variables; Bindings: Env }>().get("/notion/import", async (c) => {
- const user = c.get("user");
- if (!user) {
- return c.json({ error: "Unauthorized" }, 401);
- }
-
- // Create SSE stream
- const stream = new TransformStream();
- const writer = stream.writable.getWriter();
- const encoder = new TextEncoder();
-
- // Create response first so client gets headers immediately
- const response = new Response(stream.readable, {
- headers: {
- "Content-Type": "text/event-stream",
- "Cache-Control": "no-cache",
- Connection: "keep-alive",
- // Required CORS headers for SSE
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Credentials": "true",
- },
- });
-
- const sendMessage = async (data: Record<string, any>) => {
- // Proper SSE format requires "data: " prefix and double newline
- const formattedData = `data: ${JSON.stringify(data)}\n\n`;
- await writer.write(encoder.encode(formattedData));
- };
-
- // Start processing in background
- c.executionCtx.waitUntil(
- (async () => {
- try {
- // Send initial heartbeat
- await sendMessage({ type: "connected" });
-
- const token = await getDecryptedKV(
- c.env.ENCRYPTED_TOKENS,
- `${user.uuid}-notion`,
- `${c.env.WORKOS_COOKIE_PASSWORD}-${user.uuid}`
- );
-
- const stringToken = new TextDecoder().decode(token);
- if (!stringToken) {
- await sendMessage({ type: "error", error: "No token found" });
- await writer.close();
- return;
- }
+ const sendMessage = async (data: Record<string, any>) => {
+ // Proper SSE format requires "data: " prefix and double newline
+ const formattedData = `data: ${JSON.stringify(data)}\n\n`;
+ await writer.write(encoder.encode(formattedData));
+ };
+
+ // Start processing in background
+ c.executionCtx.waitUntil(
+ (async () => {
+ try {
+ // Send initial heartbeat
+ await sendMessage({ type: "connected" });
+
+ const token = await getDecryptedKV(
+ c.env.ENCRYPTED_TOKENS,
+ `${user.uuid}-notion`,
+ `${c.env.WORKOS_COOKIE_PASSWORD}-${user.uuid}`
+ );
+
+ const stringToken = new TextDecoder().decode(token);
+ if (!stringToken) {
+ await sendMessage({ type: "error", error: "No token found" });
+ await writer.close();
+ return;
+ }
- await sendMessage({ type: "progress", progress: 5 });
-
- // Fetch pages with progress updates
- const pages = await getAllNotionPageContents(
- stringToken,
- async (progress) => {
- // Map progress from 0-100 to 5-40 range
- const scaledProgress = Math.floor(5 + (progress * 35) / 100);
- await sendMessage({ type: "progress", progress: scaledProgress });
- }
- );
-
- await sendMessage({ type: "progress", progress: 40 });
-
- let processed = 0;
- const totalPages = pages.length;
-
- const db = database(c.env.HYPERDRIVE.connectionString);
-
- for (const page of pages) {
- // Calculate document hash for duplicate detection
- const encoder = new TextEncoder();
- const data = encoder.encode(page.content);
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
- const hashArray = Array.from(new Uint8Array(hashBuffer));
- const documentHash = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
-
- // Check for duplicates using hash
- const existingDocs = await db
- .select()
- .from(documents)
- .where(
- and(
- eq(documents.userId, user.id),
- or(
- eq(documents.contentHash, documentHash),
- and(
- eq(documents.type, "notion"),
- or(
- eq(documents.url, page.url),
- eq(documents.raw, page.content)
- )
+ await sendMessage({ type: "progress", progress: 5 });
+
+ // Fetch pages with progress updates
+ const pages = await getAllNotionPageContents(
+ stringToken,
+ async (progress) => {
+ // Map progress from 0-100 to 5-40 range
+ const scaledProgress = Math.floor(5 + (progress * 35) / 100);
+ await sendMessage({ type: "progress", progress: scaledProgress });
+ }
+ );
+
+ await sendMessage({ type: "progress", progress: 40 });
+
+ let processed = 0;
+ const totalPages = pages.length;
+
+ const db = database(c.env.HYPERDRIVE.connectionString);
+
+ for (const page of pages) {
+ // Calculate document hash for duplicate detection
+ const encoder = new TextEncoder();
+ const data = encoder.encode(page.content);
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ const documentHash = hashArray
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join("");
+
+ // Check for duplicates using hash
+ const existingDocs = await db
+ .select()
+ .from(documents)
+ .where(
+ and(
+ eq(documents.userId, user.id),
+ or(
+ eq(documents.contentHash, documentHash),
+ and(
+ eq(documents.type, "notion"),
+ or(
+ eq(documents.url, page.url),
+ eq(documents.raw, page.content)
)
)
)
- );
-
- if (existingDocs.length > 0) {
- await sendMessage({
- type: "warning",
- message: `Skipping duplicate page: ${page.title}`
- });
- processed++;
- continue;
- }
-
- // Insert into documents table first
- try {
- await db.insert(documents).values({
- uuid: page.id,
+ )
+ );
+
+ if (existingDocs.length > 0) {
+ await sendMessage({
+ type: "warning",
+ message: `Skipping duplicate page: ${page.title}`,
+ });
+ processed++;
+ continue;
+ }
+
+ // Insert into documents table first
+ try {
+ await db.insert(documents).values({
+ uuid: page.id,
+ userId: user.id,
+ type: "notion",
+ url: page.url,
+ title: page.title,
+ contentHash: documentHash,
+ raw: page.content,
+ });
+
+ await c.env.CONTENT_WORKFLOW.create({
+ params: {
userId: user.id,
+ content: page.url,
+ spaces: [],
type: "notion",
+ uuid: page.id,
url: page.url,
- title: page.title,
- contentHash: documentHash,
- raw: page.content,
- });
-
- await c.env.CONTENT_WORKFLOW.create({
- params: {
- userId: user.id,
- content: page.url,
- spaces: [],
+ prefetched: {
+ contentToVectorize: page.content,
+ contentToSave: page.content,
+ title: page.title,
type: "notion",
- uuid: page.id,
- url: page.url,
- prefetched: {
- contentToVectorize: page.content,
- contentToSave: page.content,
- title: page.title,
- type: "notion",
- },
- createdAt: page.createdAt,
},
- id: `${user.id}-${page.id}-${new Date().getTime()}`,
- });
-
- processed++;
- const progress = 50 + Math.floor((processed / totalPages) * 50);
- await sendMessage({ type: "progress", progress, page: page.title });
-
- } catch (error) {
- console.error(`Failed to process page ${page.title}:`, error);
- await sendMessage({
- type: "warning",
- message: `Failed to process page: ${page.title}`,
- error: error instanceof Error ? error.message : "Unknown error"
- });
- processed++;
- continue;
- }
+ createdAt: page.createdAt,
+ },
+ id: `${user.id}-${page.id}-${new Date().getTime()}`,
+ });
+
+ processed++;
+ const progress = 50 + Math.floor((processed / totalPages) * 50);
+ await sendMessage({ type: "progress", progress, page: page.title });
+ } catch (error) {
+ console.error(`Failed to process page ${page.title}:`, error);
+ await sendMessage({
+ type: "warning",
+ message: `Failed to process page: ${page.title}`,
+ error: error instanceof Error ? error.message : "Unknown error",
+ });
+ processed++;
+ continue;
}
-
- await sendMessage({ type: "complete", progress: 100 });
- await writer.close();
- } catch (error) {
- console.error("Import error:", error);
- await sendMessage({
- type: "error",
- error: error instanceof Error ? error.message : "Import failed",
- });
- await writer.close();
}
- })()
- );
-
- return response;
- });
-export default integrations; \ No newline at end of file
+ await sendMessage({ type: "complete", progress: 100 });
+ await writer.close();
+ } catch (error) {
+ console.error("Import error:", error);
+ await sendMessage({
+ type: "error",
+ error: error instanceof Error ? error.message : "Import failed",
+ });
+ await writer.close();
+ }
+ })()
+ );
+
+ return response;
+});
+
+export default integrations;
diff --git a/apps/backend/src/routes/memories.ts b/apps/backend/src/routes/memories.ts
index 48d16f74..f895bcbf 100644
--- a/apps/backend/src/routes/memories.ts
+++ b/apps/backend/src/routes/memories.ts
@@ -9,8 +9,11 @@ import {
contentToSpace,
} from "@supermemory/db/schema";
import { and, database, desc, eq, or, sql, isNull } from "@supermemory/db";
+import { fromHono } from "chanfana";
-const memories = new Hono<{ Variables: Variables; Bindings: Env }>()
+const memories = fromHono(new Hono<{ Variables: Variables; Bindings: Env }>(), {
+ base: "",
+})
.get(
"/",
zValidator(
diff --git a/apps/backend/src/routes/spaces.ts b/apps/backend/src/routes/spaces.ts
index cb0cf417..c051217a 100644
--- a/apps/backend/src/routes/spaces.ts
+++ b/apps/backend/src/routes/spaces.ts
@@ -12,8 +12,14 @@ import {
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
import { randomId } from "@supermemory/shared";
-
-const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>()
+import { fromHono } from "chanfana";
+
+const spacesRoute = fromHono(
+ new Hono<{ Variables: Variables; Bindings: Env }>(),
+ {
+ base: "",
+ }
+)
.get("/", async (c) => {
const user = c.get("user");
if (!user) {
diff --git a/apps/backend/src/routes/user.ts b/apps/backend/src/routes/user.ts
index 0d45737f..761b2fa4 100644
--- a/apps/backend/src/routes/user.ts
+++ b/apps/backend/src/routes/user.ts
@@ -11,8 +11,11 @@ import {
import { decryptApiKey, getApiKey } from "../auth";
import { DurableObjectStore } from "@hono-rate-limiter/cloudflare";
import { rateLimiter } from "hono-rate-limiter";
+import { fromHono } from "chanfana";
-const user = new Hono<{ Variables: Variables; Bindings: Env }>()
+const user = fromHono(new Hono<{ Variables: Variables; Bindings: Env }>(), {
+ base: "",
+})
.get("/", (c) => {
return c.json(c.get("user"));
})
@@ -132,30 +135,23 @@ const user = new Hono<{ Variables: Variables; Bindings: Env }>()
return c.json({ invitations });
})
- .get(
- "/key",
- async (c) => {
- const user = c.get("user");
- if (!user) {
- return c.json({ error: "Unauthorized" }, 401);
- }
+ .get("/key", async (c) => {
+ const user = c.get("user");
+ if (!user) {
+ return c.json({ error: "Unauthorized" }, 401);
+ }
- // we need user.id and user.lastApiKeyGeneratedAt
- const lastApiKeyGeneratedAt = user.lastApiKeyGeneratedAt?.getTime();
- if (!lastApiKeyGeneratedAt) {
- return c.json({ error: "No API key generated" }, 400);
- }
+ // we need user.id and user.lastApiKeyGeneratedAt
+ const lastApiKeyGeneratedAt = user.lastApiKeyGeneratedAt?.getTime();
+ if (!lastApiKeyGeneratedAt) {
+ return c.json({ error: "No API key generated" }, 400);
+ }
- const key = await getApiKey(
- user.uuid,
- lastApiKeyGeneratedAt.toString(),
- c
- );
+ const key = await getApiKey(user.uuid, lastApiKeyGeneratedAt.toString(), c);
- const decrypted = await decryptApiKey(key, c);
- return c.json({ key, decrypted });
- }
- )
+ const decrypted = await decryptApiKey(key, c);
+ return c.json({ key, decrypted });
+ })
.post("/update", async (c) => {
const user = c.get("user");
if (!user) {
@@ -194,6 +190,6 @@ const user = new Hono<{ Variables: Variables; Bindings: Env }>()
.where(eq(users.id, user.id));
return c.json({ success: true });
- })
+ });
export default user;