From e05c644143504b9f105be7caa962573890e7285c Mon Sep 17 00:00:00 2001 From: Kush Thaker Date: Tue, 10 Sep 2024 13:20:00 +0530 Subject: [PATCH] feat: add loading card for memories page --- apps/cf-ai-backend/src/index.ts | 53 +- .../src/queueConsumer/chunkers/chunkTweet.ts | 2 - .../src/queueConsumer/helpers/processPage.ts | 1 - .../src/queueConsumer/helpers/processTweet.ts | 1 - apps/cf-ai-backend/src/queueConsumer/index.ts | 88 +- apps/cf-ai-backend/src/types.ts | 1 + apps/web/app/(dash)/(memories)/content.tsx | 104 +- .../(memories)/space/[spaceid]/page.tsx | 2 +- apps/web/app/(dash)/menu.tsx | 13 +- apps/web/app/actions/doers.ts | 244 --- apps/web/app/actions/fetchers.ts | 3 +- apps/web/app/api/getJobCount/route.ts | 33 + apps/web/app/api/getNewMemories/route.ts | 46 + apps/web/app/api/store/mobile/route.ts | 52 + apps/web/app/layout.tsx | 26 +- apps/web/app/providers.tsx | 9 + .../components/loadingCard/loadingCard.tsx | 62 + apps/web/contexts/PendingJobContext.tsx | 31 + ...andemic.sql => 0000_adorable_iron_man.sql} | 11 +- apps/web/migrations/meta/0000_snapshot.json | 1946 +++++++++-------- apps/web/migrations/meta/0001_snapshot.json | 923 -------- apps/web/migrations/meta/_journal.json | 31 +- apps/web/server/encrypt.ts | 21 +- 23 files changed, 1485 insertions(+), 2218 deletions(-) create mode 100644 apps/web/app/api/getJobCount/route.ts create mode 100644 apps/web/app/api/getNewMemories/route.ts create mode 100644 apps/web/app/api/store/mobile/route.ts create mode 100644 apps/web/components/loadingCard/loadingCard.tsx create mode 100644 apps/web/contexts/PendingJobContext.tsx rename apps/web/migrations/{0000_fixed_pandemic.sql => 0000_adorable_iron_man.sql} (95%) delete mode 100644 apps/web/migrations/meta/0001_snapshot.json diff --git a/apps/cf-ai-backend/src/index.ts b/apps/cf-ai-backend/src/index.ts index 997ac44e..e4fb1011 100644 --- a/apps/cf-ai-backend/src/index.ts +++ b/apps/cf-ai-backend/src/index.ts @@ -25,7 +25,7 @@ import chunkText from "./queueConsumer/chunkers/chonker"; import { systemPrompt, template } from "./prompts/prompt1"; import { swaggerUI } from "@hono/swagger-ui"; import { database } from "./db"; -import { storedContent } from "@repo/db/schema"; +import { jobs, storedContent, users } from "@repo/db/schema"; import { sql, and, eq } from "drizzle-orm"; import { LIMITS } from "@repo/shared-types"; import { typeDecider } from "./queueConsumer/utils/typeDecider"; @@ -34,8 +34,8 @@ import { chunkNote, chunkPage, } from "./queueConsumer/chunkers/chunkPageOrNotes"; -import { queue } from "./queueConsumer"; -import { isErr } from "./errors/results"; +import { d1ErrorFactory, queue } from "./queueConsumer"; +import { isErr, wrap } from "./errors/results"; import { createOpenAI } from "@ai-sdk/openai"; const app = new Hono<{ Bindings: Env }>(); @@ -119,11 +119,33 @@ app.post("/api/add", zValidator("json", vectorBody), async (c) => { // unique contraint check if (isWithinLimit) { + //insert to the jobs table + const job = await wrap( + db + .insert(jobs) + .values({ + userId: body.user, + url: saveToDbUrl, + status: "Processing", + attempts: 0, + createdAt: new Date(), + updatedAt: new Date(), + }) + .returning({ jobId: jobs.id }), + d1ErrorFactory, + "Error When inserting into jobs table", + ); + if (isErr(job)) { + throw job.error; + } + + const jobId = job.value[0].jobId; await c.env.EMBEDCHUNKS_QUEUE.send({ content: saveToDbUrl, user: body.user, space: body.spaces, type: type, + jobId: jobId, }); } else { return c.json({ @@ -775,6 +797,31 @@ app.get("/howFuckedAreWe", async (c) => { return c.json({ fuckedPercent }); }); +app.get( + "api/memories", + zValidator( + "query", + z.object({ + userId: z.string(), + }), + ), + async (c) => { + const { userId } = c.req.valid("query"); + const db = database(c.env); + const spaces = await db.query.space.findMany({ + where: eq(users, userId), + }); + + const memories = await db.query.storedContent.findMany({ + where: eq(users, userId), + }); + const processingJobs = await db.query.jobs.findMany({ + where: and(eq(jobs.userId, userId), eq(jobs.status, "Processing")), + }); + return c.json({ spaces, memories, processingJobs }); + }, +); + export default { fetch: app.fetch, queue, diff --git a/apps/cf-ai-backend/src/queueConsumer/chunkers/chunkTweet.ts b/apps/cf-ai-backend/src/queueConsumer/chunkers/chunkTweet.ts index 46a56410..4cb35304 100644 --- a/apps/cf-ai-backend/src/queueConsumer/chunkers/chunkTweet.ts +++ b/apps/cf-ai-backend/src/queueConsumer/chunkers/chunkTweet.ts @@ -33,7 +33,6 @@ export function chunkThread(threadText: string): TweetChunks { if (typeof threadText == "string") { console.log("DA WORKER FAILED DO SOMEHTING FIX DA WROKER", thread); const rawTweet = getRawTweet(thread); - console.log(rawTweet); const parsedTweet: any = JSON.parse(rawTweet); const chunkedTweet = chunkText(parsedTweet.text, 1536); @@ -56,7 +55,6 @@ export function chunkThread(threadText: string): TweetChunks { return { type: "tweet", chunks }; } else { - console.log("thread in else statement", JSON.stringify(thread)); const chunkedTweets = (thread as any).map((tweet: Tweet) => { const chunkedTweet = chunkText(tweet.text, 1536); diff --git a/apps/cf-ai-backend/src/queueConsumer/helpers/processPage.ts b/apps/cf-ai-backend/src/queueConsumer/helpers/processPage.ts index 9a50d701..b02fc6f6 100644 --- a/apps/cf-ai-backend/src/queueConsumer/helpers/processPage.ts +++ b/apps/cf-ai-backend/src/queueConsumer/helpers/processPage.ts @@ -34,7 +34,6 @@ export async function processPage(input: { throw metadataResult.error; } const metadata = metadataResult.value; - console.log("[this is the metadata]", metadata); return Ok({ pageContent, metadata }); } catch (e) { console.error("[Page Processing Error]", e); diff --git a/apps/cf-ai-backend/src/queueConsumer/helpers/processTweet.ts b/apps/cf-ai-backend/src/queueConsumer/helpers/processTweet.ts index 8d83f2dc..21eed22b 100644 --- a/apps/cf-ai-backend/src/queueConsumer/helpers/processTweet.ts +++ b/apps/cf-ai-backend/src/queueConsumer/helpers/processTweet.ts @@ -17,7 +17,6 @@ export const getTweetData = async ( tweetID: string, ): Promise> => { try { - console.log("is fetch defined here?"); const url = `https://cdn.syndication.twimg.com/tweet-result?id=${tweetID}&lang=en&features=tfw_timeline_list%3A%3Btfw_follower_count_sunset%3Atrue%3Btfw_tweet_edit_backend%3Aon%3Btfw_refsrc_session%3Aon%3Btfw_fosnr_soft_interventions_enabled%3Aon%3Btfw_show_birdwatch_pivots_enabled%3Aon%3Btfw_show_business_verified_badge%3Aon%3Btfw_duplicate_scribes_to_settings%3Aon%3Btfw_use_profile_image_shape_enabled%3Aon%3Btfw_show_blue_verified_badge%3Aon%3Btfw_legacy_timeline_sunset%3Atrue%3Btfw_show_gov_verified_badge%3Aon%3Btfw_show_business_affiliate_badge%3Aon%3Btfw_tweet_edit_frontend%3Aon&token=4c2mmul6mnh`; const resp = await fetch(url, { diff --git a/apps/cf-ai-backend/src/queueConsumer/index.ts b/apps/cf-ai-backend/src/queueConsumer/index.ts index fec5260f..763f8229 100644 --- a/apps/cf-ai-backend/src/queueConsumer/index.ts +++ b/apps/cf-ai-backend/src/queueConsumer/index.ts @@ -35,7 +35,7 @@ class D1InsertError extends BaseError { } } -const d1ErrorFactory = (err: Error, source: string) => +export const d1ErrorFactory = (err: Error, source: string) => new D1InsertError(err.message, source); const calculateExponentialBackoff = ( @@ -52,11 +52,12 @@ export async function queue( space: Array; user: string; type: string; + jobId: number; }>, env: Env, ): Promise { const db = database(env); - + console.log("[This is the batch]", JSON.stringify(batch)); for (let message of batch.messages) { const body = message.body; @@ -71,63 +72,38 @@ export async function queue( throw userExists.error; } - //check if this is a retry job.. by checking if the combination of the userId and the url already exists on the queue - let jobId; - const existingJob = await wrap( - db - .select() - .from(jobs) - .where( - and( - eq(jobs.userId, userExists.value[0].id), - eq(jobs.url, body.content), - ), - ) - .limit(1), + //take the job id from the body and update the job table with attempts + 1 + const jobId = body.jobId; + + const jobResult = await wrap( + db.select().from(jobs).where(eq(jobs.id, jobId)).limit(1), d1ErrorFactory, - "Error when checking for existing job", + "Error when fetching job", ); - if (isErr(existingJob)) { - throw existingJob.error; + if (isErr(jobResult)) { + throw jobResult.error; } - if (existingJob.value.length > 0) { - jobId = existingJob.value[0].id; - await wrap( - db - .update(jobs) - .set({ - attempts: existingJob.value[0].attempts + 1, - updatedAt: new Date(), - status: "Processing", - }) - .where(eq(jobs.id, jobId)), - d1ErrorFactory, - "Error when updating job attempts", - ); - } else { - const job = await wrap( - db - .insert(jobs) - .values({ - userId: userExists.value[0].id as string, - url: body.content, - status: "Processing", - attempts: 1, - createdAt: new Date(), - updatedAt: new Date(), - }) - .returning({ jobId: jobs.id }), - d1ErrorFactory, - "Error When inserting into jobs table", - ); - if (isErr(job)) { - throw job.error; - } - jobId = job.value[0].jobId; + const job = jobResult.value[0]; + + if (!job) { + throw new Error("Job not found"); } + await wrap( + db + .update(jobs) + .set({ + attempts: job.attempts + 1, + updatedAt: new Date(), + status: "Processing", + }) + .where(eq(jobs.id, jobId)), + d1ErrorFactory, + "Error when updating job attempts", + ); + let pageContent: string; let vectorData: string; let metadata: Metadata; @@ -136,7 +112,6 @@ export async function queue( let noteId = 0; switch (type) { case "note": { - console.log("note hit"); const note = processNote(body.content); if (isErr(note)) { throw note.error; @@ -149,13 +124,11 @@ export async function queue( break; } case "page": { - console.log("page hit"); const page = await processPage({ url: body.content, securityKey: env.MD_SEC_KEY, }); if (isErr(page)) { - console.log("there is a page error here"); throw page.error; } pageContent = page.value.pageContent; @@ -172,7 +145,7 @@ export async function queue( tweetUrl: body.content, env: env, }); - console.log("[This is the thread]", thread); + if (isErr(tweet)) { throw tweet.error; } @@ -368,9 +341,8 @@ export async function queue( .update(jobs) .set({ status: "Processed" }) .where(eq(jobs.id, jobId)); - - return; } + console.log(`Finished processing batch of ${batch.messages.length} messages`); } /* diff --git a/apps/cf-ai-backend/src/types.ts b/apps/cf-ai-backend/src/types.ts index 93a548d2..59471a01 100644 --- a/apps/cf-ai-backend/src/types.ts +++ b/apps/cf-ai-backend/src/types.ts @@ -30,6 +30,7 @@ export interface JobData { space: Array; user: string; type: string; + jobId: number; } export interface TweetData { diff --git a/apps/web/app/(dash)/(memories)/content.tsx b/apps/web/app/(dash)/(memories)/content.tsx index 9dba3637..1a9a74f7 100644 --- a/apps/web/app/(dash)/(memories)/content.tsx +++ b/apps/web/app/(dash)/(memories)/content.tsx @@ -1,6 +1,6 @@ "use client"; -import { Content, StoredSpace } from "@repo/db/schema"; +import { Content, Job, jobs, StoredSpace } from "@repo/db/schema"; import { MemoriesIcon, NextIcon, UrlIcon } from "@repo/ui/icons"; import { ArrowLeftIcon, @@ -12,10 +12,11 @@ import { } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import React, { useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import Masonry from "react-layout-masonry"; import { getRawTweet } from "@repo/shared-types/utils"; import { MyTweet } from "../../../components/twitter/render-tweet"; +import LoadingCard from "@/components/loadingCard/loadingCard"; import { DropdownMenu, DropdownMenuContent, @@ -53,14 +54,25 @@ import { DocumentTextIcon, FolderIcon, } from "@heroicons/react/24/outline"; +import { db } from "@/server/db"; +import { and, count, eq } from "drizzle-orm"; +import { usePendingJob } from "../../../contexts/PendingJobContext"; type TMemoriesPage = { - memoriesAndSpaces: { memories: Content[]; spaces: StoredSpace[] }; + memoriesAndSpaces: { + memories: Content[]; + spaces: StoredSpace[]; + userId: string; + }; title?: string; currentSpace?: StoredSpace; usersWithAccess?: string[]; }; +interface JobCountResponse { + pendingJobs: number; +} + export function MemoriesPage({ memoriesAndSpaces, title = "Your Memories", @@ -78,8 +90,65 @@ export function MemoriesPage({ return "All"; }, [tab]); + const userId = memoriesAndSpaces.userId; const [filter, setFilter] = useState(initialFilter); const [spaces, setSpaces] = useState(memoriesAndSpaces.spaces); + const [newMemories, setNewMemories] = useState([]); + const [lastFetchTimestamp, setLastFetchTimestamp] = useState( + new Date().toISOString(), + ); + + const { pendingJobs, setPendingJobs } = usePendingJob(); + const fetchNewMemories = async (lastFetchTimestamp: string) => { + try { + const response = await fetch( + "/api/getNewMemories?lastFetchTimestamp=" + lastFetchTimestamp, + ); + if (!response.ok) { + throw new Error("Failed to fetch new memories"); + } + const data = (await response.json()) as { newMemories: Content[] }; + return data.newMemories; + } catch (error) { + console.error("Error fetching new memories:", error); + } + }; + + useEffect(() => { + const fetchJobCount = async () => { + try { + const response = await fetch("/api/getJobCount"); + if (!response.ok) { + throw new Error("Failed to fetch job count"); + } + const data: JobCountResponse = await response.json(); + const newPendingJobs = data.pendingJobs; + + // If pendingJobs has decreased, fetch new memories with the lastFetchTimestamp + if (newPendingJobs < pendingJobs) { + const newMemories = await fetchNewMemories(lastFetchTimestamp); + if (newMemories) { + setNewMemories((prev) => [...prev, ...newMemories]); + setLastFetchTimestamp(new Date().toISOString()); + } + } + + setPendingJobs(newPendingJobs); + } catch (error) { + console.error("Error fetching job count:", error); + } + }; + + fetchJobCount(); // Initial fetch + const intervalId = setInterval(fetchJobCount, 5000); // Poll every 5 seconds + + return () => clearInterval(intervalId); // Clean up on unmount + }, [ + pendingJobs, + memoriesAndSpaces.memories, + setPendingJobs, + lastFetchTimestamp, + ]); const handleDeleteSpace = async (id: number) => { const response = await deleteSpace(id); @@ -102,7 +171,6 @@ export function MemoriesPage({ const json = JSON.stringify(dataToExport, null, 2); const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); - const a = document.createElement("a"); a.href = url; a.download = "memories_and_spaces.json"; @@ -115,6 +183,11 @@ export function MemoriesPage({ const sortedItems = useMemo(() => { // merge spaces & memories to { item: "memory" | "space", date: Date, data: Content | StoredSpace } const unifiedItems = [ + ...newMemories.map((memory) => ({ + item: "memory", + date: new Date(memory.savedAt), + data: memory, + })), ...memoriesAndSpaces.memories.map((memory) => ({ item: "memory", date: new Date(memory.savedAt), @@ -152,18 +225,19 @@ export function MemoriesPage({ return false; }) .sort((a, b) => b.date - a.date); - }, [memoriesAndSpaces.memories, spaces, filter]); + }, [memoriesAndSpaces.memories, spaces, filter, newMemories]); return (
{currentSpace && ( - )} +

{title}

@@ -177,6 +251,7 @@ export function MemoriesPage({ currentSpace ? SpaceFilterMethods : MemoriesFilterMethods } /> +
))} @@ -211,6 +287,7 @@ export function MemoriesPage({ if (!email) { toast.error("Please enter an email"); + return; } @@ -229,6 +306,7 @@ export function MemoriesPage({ name="email" placeholder="Add user by email" /> +
@@ -242,10 +320,17 @@ export function MemoriesPage({ className: "min-w-[calc(33.3333%-16px)] w-full", }} > + {pendingJobs > 0 && ( +
+ +
+ )} + {sortedItems.map((item) => { if (item.item === "memory") { return ( -
+
{ handleDeleteSpace(id); @@ -446,7 +532,7 @@ function MemoryComponent({ } const MemoriesFilterMethods = ["All", "Spaces", "Pages", "Notes", "Tweet"]; -const SpaceFilterMethods = ["All", "Pages", "Notes", "Tweet"]; +const SpaceFilterMethods = ["All", "Pages", "Notes", "Tweet", "Jobs"]; function Filters({ setFilter, filter, @@ -463,7 +549,7 @@ function Filters({
diff --git a/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx b/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx index 8ad9d9cc..c64c58b1 100644 --- a/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx +++ b/apps/web/app/(dash)/(memories)/space/[spaceid]/page.tsx @@ -18,7 +18,7 @@ async function Page({ params: { spaceid } }: { params: { spaceid: number } }) { return ( x.userEmail) ?? []} diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx index 45cfc599..9c7db713 100644 --- a/apps/web/app/(dash)/menu.tsx +++ b/apps/web/app/(dash)/menu.tsx @@ -31,6 +31,7 @@ import ComboboxWithCreate from "@repo/ui/shadcn/combobox"; import { StoredSpace } from "@repo/db/schema"; import { useKeyPress } from "@/lib/useKeyPress"; import { useFormStatus } from "react-dom"; +import { usePendingJob } from "@/contexts/PendingJobContext"; function Menu() { useKeyPress("a", () => { @@ -218,7 +219,7 @@ function DialogContentMenu({ setDialogClose }: { setDialogClose: () => void }) { setSelectedSpaces([]); return cont; }; - + const { pendingJobs, setPendingJobs } = usePendingJob(); const formSubmit = () => { toast.promise(handleSubmit(content, selectedSpaces), { loading: ( @@ -227,8 +228,14 @@ function DialogContentMenu({ setDialogClose }: { setDialogClose: () => void }) { Creating memory... ), - success: (data) => "Memory queued", - error: (error) => `Memory creation failed: ${error}`, + success: (data) => { + setPendingJobs(pendingJobs + 1); + return "Memory queued"; + }, + error: (error) => { + setPendingJobs(pendingJobs - 1); + return `Memory creation failed: ${error}`; + }, richColors: true, }); }; diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts index c11d5f0a..49549776 100644 --- a/apps/web/app/actions/doers.ts +++ b/apps/web/app/actions/doers.ts @@ -218,250 +218,6 @@ export const createMemory = async (input: { }; } - // const type = typeDecider(input.content); - - // let pageContent = input.content; - // let metadata: Awaited>; - // let vectorData: string; - - // if (!(await limit(data.user.id, type))) { - // return { - // success: false, - // data: 0, - // error: `You have exceeded the limit of ${LIMITS[type as keyof typeof LIMITS]} ${type}s.`, - // }; - // } --> How would this fit in the backend??? - - // let noteId = 0; - - // if (type === "page") { - // const response = await fetch("https://md.dhr.wtf/?url=" + input.content, { - // headers: { - // Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, - // }, - // }); - // pageContent = await response.text(); - // vectorData = pageContent; - // try { - // metadata = await getMetaData(input.content); - // } catch (e) { - // return { - // success: false, - // error: "Failed to fetch metadata for the page. Please try again later.", - // }; - // } - // } else if (type === "tweet") { - // //Request the worker for the entire thread - - // let thread: string; - // let errorOccurred: boolean = false; - - // try { - // const cf_thread_endpoint = process.env.THREAD_CF_WORKER; - // const authKey = process.env.THREAD_CF_AUTH; - // const threadRequest = await fetch(cf_thread_endpoint, { - // method: "POST", - // headers: { - // "Content-Type": "application/json", - // Authorization: authKey, - // }, - // body: JSON.stringify({ url: input.content }), - // }); - - // if (threadRequest.status !== 200) { - // throw new Error( - // `Failed to fetch the thread: ${input.content}, Reason: ${threadRequest.statusText}`, - // ); - // } - - // thread = await threadRequest.text(); - // if (thread.trim().length === 2) { - // console.log("Thread is an empty array"); - // throw new Error( - // "[THREAD FETCHING SERVICE] Got no content form thread worker", - // ); - // } - // } catch (e) { - // console.log("[THREAD FETCHING SERVICE] Failed to fetch the thread", e); - // errorOccurred = true; - // } - - // const tweet = await getTweetData(input.content.split("/").pop() as string); - - // pageContent = tweetToMd(tweet); - // console.log("THis ishte page content!!", pageContent); - // //@ts-ignore - // vectorData = errorOccurred ? JSON.stringify(pageContent) : thread; - // metadata = { - // baseUrl: input.content, - // description: tweet.text.slice(0, 200), - // image: tweet.user.profile_image_url_https, - // title: `Tweet by ${tweet.user.name}`, - // }; - // } else if (type === "note") { - // pageContent = input.content; - // vectorData = pageContent; - // noteId = new Date().getTime(); - // metadata = { - // baseUrl: `https://supermemory.ai/note/${noteId}`, - // description: `Note created at ${new Date().toLocaleString()}`, - // image: "https://supermemory.ai/logo.png", - // title: `${pageContent.slice(0, 20)} ${pageContent.length > 20 ? "..." : ""}`, - // }; - // } else { - // return { - // success: false, - // data: 0, - // error: "Invalid type", - // }; - // } - - // let storeToSpaces = input.spaces; - - // if (!storeToSpaces) { - // storeToSpaces = []; - // } - - // const vectorSaveResponse = await fetch( - // `${process.env.BACKEND_BASE_URL}/api/add`, - // { - // method: "POST", - // body: JSON.stringify({ - // pageContent: vectorData, - // title: metadata.title, - // description: metadata.description, - // url: metadata.baseUrl, - // spaces: storeToSpaces.map((spaceId) => spaceId.toString()), - // user: data.user.id, - // 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; - - // const response = (await vectorSaveResponse.json()) as { - // status: string; - // chunkedInput: string; - // message?: string; - // }; - - // try { - // if (response.status !== "ok") { - // if (response.status === "error") { - // return { - // success: false, - // data: 0, - // error: response.message, - // }; - // } else { - // return { - // success: false, - // data: 0, - // error: `Failed to save to vector store. Backend returned error: ${response.message}`, - // }; - // } - // } - // } catch (e) { - // return { - // success: false, - // data: 0, - // error: `Failed to save to vector store. Backend returned error: ${e}`, - // }; - // } - - // const saveToDbUrl = - // (metadata.baseUrl.split("#supermemory-user-")[0] ?? metadata.baseUrl) + - // "#supermemory-user-" + - // data.user.id; - - // // Insert into database - // try { - // const insertResponse = await db - // .insert(storedContent) - // .values({ - // content: pageContent, - // title: metadata.title, - // description: metadata.description, - // url: saveToDbUrl, - // baseUrl: saveToDbUrl, - // image: metadata.image, - // savedAt: new Date(), - // userId: data.user.id, - // type, - // noteId, - // }) - // .returning({ id: storedContent.id }); - // revalidatePath("/memories"); - // revalidatePath("/home"); - - // if (!insertResponse[0]?.id) { - // return { - // success: false, - // data: 0, - // error: "Something went wrong while saving the document to the database", - // }; - // } - - // 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 (storeToSpaces.length > 0) { - // // Adding the many-to-many relationship between content and spaces - // const spaceData = await db - // .select() - // .from(space) - // .where( - // and(inArray(space.id, storeToSpaces), eq(space.user, data.user.id)), - // ) - // .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 }); - // }), - // ); - // } - return { success: true, data: 1, diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts index f00feb3c..bed930c5 100644 --- a/apps/web/app/actions/fetchers.ts +++ b/apps/web/app/actions/fetchers.ts @@ -9,6 +9,7 @@ import { chatThreads, Content, contentToSpace, + Job, space, spacesAccess, storedContent, @@ -158,10 +159,10 @@ export const getMemoriesInsideSpace = async ( }, }; }; - export const getAllUserMemoriesAndSpaces = async (): ServerActionReturnType<{ spaces: StoredSpace[]; memories: Content[]; + userId: string; }> => { const data = await auth(); diff --git a/apps/web/app/api/getJobCount/route.ts b/apps/web/app/api/getJobCount/route.ts new file mode 100644 index 00000000..75c04c6b --- /dev/null +++ b/apps/web/app/api/getJobCount/route.ts @@ -0,0 +1,33 @@ +import { db } from "@/server/db"; +import { and, eq, sql } from "drizzle-orm"; +import { jobs } from "@repo/db/schema"; +import { type NextRequest, NextResponse } from "next/server"; +import { ensureAuth } from "../ensureAuth"; + +export const runtime = "edge"; + +export async function GET(req: NextRequest) { + const session = await ensureAuth(req); + + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + + // Calculate the timestamp for one hour ago + const oneHourAgoTimestamp = Math.floor(Date.now() / 1000) - 3600; + + const jobCount = await db + .select({ count: sql`count(*)`.mapWith(Number) }) + .from(jobs) + .where( + and( + eq(jobs.userId, session.user.id), + eq(jobs.status, "Processing"), + sql`${jobs.updatedAt} > ${oneHourAgoTimestamp}`, + ), + ); + + return NextResponse.json({ + pendingJobs: jobCount[0]?.count ?? 0, + }); +} diff --git a/apps/web/app/api/getNewMemories/route.ts b/apps/web/app/api/getNewMemories/route.ts new file mode 100644 index 00000000..edb61c4b --- /dev/null +++ b/apps/web/app/api/getNewMemories/route.ts @@ -0,0 +1,46 @@ +import { db } from "@/server/db"; +import { storedContent } from "@repo/db/schema"; +import { eq, gt } from "drizzle-orm"; +import { ensureAuth } from "../ensureAuth"; +import { type NextRequest, NextResponse } from "next/server"; + +export const runtime = "edge"; + +export async function GET(req: NextRequest) { + const session = await ensureAuth(req); + + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + const lastFetchTimestamp = req.nextUrl.searchParams.get("lastFetchTimestamp"); + + if (!lastFetchTimestamp) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 }, + ); + } + + if (!lastFetchTimestamp) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 }, + ); + } + + try { + const newMemories = await db.query.storedContent.findMany({ + where: (content) => + eq(content.userId, session.user.id) && + gt(content.savedAt, new Date(lastFetchTimestamp)), + }); + + return NextResponse.json({ newMemories }); + } catch (error) { + console.error("Error fetching new memories:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/apps/web/app/api/store/mobile/route.ts b/apps/web/app/api/store/mobile/route.ts new file mode 100644 index 00000000..a470a05e --- /dev/null +++ b/apps/web/app/api/store/mobile/route.ts @@ -0,0 +1,52 @@ +import { NextRequest } from "next/server"; +import { z } from "zod"; +import { ensureAuth } from "../../ensureAuth"; + +export const runtime = "edge"; + +const vectorBody = z.object({ + spaces: z.array(z.number()).optional(), + url: z.string(), +}); + +export async function POST(req: NextRequest) { + const session = await ensureAuth(req); + + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + + try { + const body = await req.json(); + const validatedBody = vectorBody.parse(body); + const vectorSaveResponses = await fetch( + `${process.env.BACKEND_BASE_URL}/api/add`, + { + method: "POST", + body: JSON.stringify({ + url: validatedBody.url, + spaces: validatedBody.spaces, + user: session.user.id, + }), + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, + }, + }, + ); + const response = (await vectorSaveResponses.json()) as { + status: string; + message?: string; + }; + + if (response.status !== "ok") { + return new Response("Internal server error", { status: 500 }); + } + + if (response.status === "ok") { + return new Response("Added to queue", { status: 200 }); + } + } catch (e) { + return new Response("Bad Request", { status: 400 }); + } +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index f74ee232..4d957a0a 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -6,7 +6,7 @@ import { GeistSans } from "geist/font/sans"; import { GeistMono } from "geist/font/mono"; import { cn } from "@repo/ui/lib/utils"; import { Toaster } from "@repo/ui/shadcn/toaster"; -import { PHProvider } from "./providers"; +import { PendingJobsProvider, PHProvider } from "./providers"; import PostHogPageView from "./PHPageViews"; const inter = Inter({ subsets: ["latin"] }); @@ -78,17 +78,19 @@ export default function RootLayout({ > - - {children} - - {/* */} - + + + {children} + + {/* */} + + ); diff --git a/apps/web/app/providers.tsx b/apps/web/app/providers.tsx index 13e41647..b9e952d5 100644 --- a/apps/web/app/providers.tsx +++ b/apps/web/app/providers.tsx @@ -1,5 +1,6 @@ // app/providers.tsx "use client"; +import { PendingJobProvider } from "@/contexts/PendingJobContext"; import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; @@ -14,3 +15,11 @@ if (typeof window !== "undefined") { export function PHProvider({ children }: { children: React.ReactNode }) { return {children}; } + +export function PendingJobsProvider({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/apps/web/components/loadingCard/loadingCard.tsx b/apps/web/components/loadingCard/loadingCard.tsx new file mode 100644 index 00000000..9cb8327b --- /dev/null +++ b/apps/web/components/loadingCard/loadingCard.tsx @@ -0,0 +1,62 @@ +import { motion } from "framer-motion"; + +const LoadingCard = ({ pendingJobs }: { pendingJobs: number }) => { + if (pendingJobs === 0) { + return null; + } + + return ( + +
+
+ +
+
+ +

One moment

+

+ Saving {pendingJobs} {pendingJobs === 1 ? "memory" : "memories"} for + you +

+
+
+ ); +}; + +export default LoadingCard; diff --git a/apps/web/contexts/PendingJobContext.tsx b/apps/web/contexts/PendingJobContext.tsx new file mode 100644 index 00000000..2f94a6ea --- /dev/null +++ b/apps/web/contexts/PendingJobContext.tsx @@ -0,0 +1,31 @@ +"use client"; +import React, { createContext, useState, useContext } from "react"; + +interface PendingJobContextType { + pendingJobs: number; + setPendingJobs: (pendingJobs: number) => void; +} + +const PendingJobContext = createContext( + undefined, +); + +export const PendingJobProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [pendingJobs, setPendingJobs] = useState(0); + + return ( + + {children} + + ); +}; + +export const usePendingJob = () => { + const context = useContext(PendingJobContext); + if (context === undefined) { + throw new Error("usePendingJob must be used within a PendingJobProvider"); + } + return context; +}; diff --git a/apps/web/migrations/0000_fixed_pandemic.sql b/apps/web/migrations/0000_adorable_iron_man.sql similarity index 95% rename from apps/web/migrations/0000_fixed_pandemic.sql rename to apps/web/migrations/0000_adorable_iron_man.sql index 09b5431a..8af12cb6 100644 --- a/apps/web/migrations/0000_fixed_pandemic.sql +++ b/apps/web/migrations/0000_adorable_iron_man.sql @@ -43,7 +43,7 @@ CREATE TABLE `chatHistory` ( `answerParts` text, `answerSources` text, `answerJustification` text, - `createdAt` integer DEFAULT '"2024-07-31T07:35:53.819Z"' NOT NULL, + `createdAt` integer DEFAULT '"2024-09-05T18:04:25.816Z"' NOT NULL, FOREIGN KEY (`threadId`) REFERENCES `chatThread`(`id`) ON UPDATE no action ON DELETE cascade ); --> statement-breakpoint @@ -70,8 +70,8 @@ CREATE TABLE `jobs` ( `attempts` integer DEFAULT 0 NOT NULL, `lastAttemptAt` integer, `error` blob, - `createdAt` integer NOT NULL, - `updatedAt` integer NOT NULL, + `createdAt` integer DEFAULT '"2024-09-05T18:04:25.816Z"' NOT NULL, + `updatedAt` integer DEFAULT '"2024-09-05T18:04:25.816Z"' NOT NULL, FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade ); --> statement-breakpoint @@ -105,8 +105,8 @@ CREATE TABLE `storedContent` ( `description` text(255), `url` text NOT NULL, `savedAt` integer NOT NULL, - `baseUrl` text(255), - `ogImage` text(255), + `baseUrl` text, + `ogImage` text, `type` text DEFAULT 'page', `image` text(255), `user` text, @@ -142,7 +142,6 @@ CREATE INDEX `jobs_url_idx` ON `jobs` (`url`);--> statement-breakpoint CREATE UNIQUE INDEX `space_name_unique` ON `space` (`name`);--> statement-breakpoint CREATE INDEX `spaces_name_idx` ON `space` (`name`);--> statement-breakpoint CREATE INDEX `spaces_user_idx` ON `space` (`user`);--> statement-breakpoint -CREATE UNIQUE INDEX `storedContent_baseUrl_unique` ON `storedContent` (`baseUrl`);--> statement-breakpoint CREATE INDEX `storedContent_url_idx` ON `storedContent` (`url`);--> statement-breakpoint CREATE INDEX `storedContent_savedAt_idx` ON `storedContent` (`savedAt`);--> statement-breakpoint CREATE INDEX `storedContent_title_idx` ON `storedContent` (`title`);--> statement-breakpoint diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json index 3bb8617f..46910f5d 100644 --- a/apps/web/migrations/meta/0000_snapshot.json +++ b/apps/web/migrations/meta/0000_snapshot.json @@ -1,926 +1,1022 @@ { - "version": "6", - "dialect": "sqlite", - "id": "3fbdb153-2764-4b09-ac22-05c3a131ec35", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "account": { - "name": "account", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "account_provider_providerAccountId_pk": { - "columns": ["provider", "providerAccountId"], - "name": "account_provider_providerAccountId_pk" - } - }, - "uniqueConstraints": {} - }, - "authenticator": { - "name": "authenticator", - "columns": { - "credentialID": { - "name": "credentialID", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "credentialPublicKey": { - "name": "credentialPublicKey", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "counter": { - "name": "counter", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "credentialDeviceType": { - "name": "credentialDeviceType", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "credentialBackedUp": { - "name": "credentialBackedUp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "transports": { - "name": "transports", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "authenticator_credentialID_unique": { - "name": "authenticator_credentialID_unique", - "columns": ["credentialID"], - "isUnique": true - } - }, - "foreignKeys": { - "authenticator_userId_user_id_fk": { - "name": "authenticator_userId_user_id_fk", - "tableFrom": "authenticator", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "authenticator_userId_credentialID_pk": { - "columns": ["credentialID", "userId"], - "name": "authenticator_userId_credentialID_pk" - } - }, - "uniqueConstraints": {} - }, - "canvas": { - "name": "canvas", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'Untitled'" - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'Untitled'" - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "''" - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "canvas_user_userId": { - "name": "canvas_user_userId", - "columns": ["userId"], - "isUnique": false - } - }, - "foreignKeys": { - "canvas_userId_user_id_fk": { - "name": "canvas_userId_user_id_fk", - "tableFrom": "canvas", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "chatHistory": { - "name": "chatHistory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "threadId": { - "name": "threadId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question": { - "name": "question", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answerParts": { - "name": "answerParts", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "answerSources": { - "name": "answerSources", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "answerJustification": { - "name": "answerJustification", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdAt": { - "name": "createdAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2024-07-31T07:35:53.819Z\"'" - } - }, - "indexes": { - "chatHistory_thread_idx": { - "name": "chatHistory_thread_idx", - "columns": ["threadId"], - "isUnique": false - } - }, - "foreignKeys": { - "chatHistory_threadId_chatThread_id_fk": { - "name": "chatHistory_threadId_chatThread_id_fk", - "tableFrom": "chatHistory", - "tableTo": "chatThread", - "columnsFrom": ["threadId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "chatThread": { - "name": "chatThread", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "firstMessage": { - "name": "firstMessage", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "chatThread_user_idx": { - "name": "chatThread_user_idx", - "columns": ["userId"], - "isUnique": false - } - }, - "foreignKeys": { - "chatThread_userId_user_id_fk": { - "name": "chatThread_userId_user_id_fk", - "tableFrom": "chatThread", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "contentToSpace": { - "name": "contentToSpace", - "columns": { - "contentId": { - "name": "contentId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "spaceId": { - "name": "spaceId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "contentToSpace_contentId_storedContent_id_fk": { - "name": "contentToSpace_contentId_storedContent_id_fk", - "tableFrom": "contentToSpace", - "tableTo": "storedContent", - "columnsFrom": ["contentId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "contentToSpace_spaceId_space_id_fk": { - "name": "contentToSpace_spaceId_space_id_fk", - "tableFrom": "contentToSpace", - "tableTo": "space", - "columnsFrom": ["spaceId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "contentToSpace_contentId_spaceId_pk": { - "columns": ["contentId", "spaceId"], - "name": "contentToSpace_contentId_spaceId_pk" - } - }, - "uniqueConstraints": {} - }, - "jobs": { - "name": "jobs", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "lastAttemptAt": { - "name": "lastAttemptAt", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "error": { - "name": "error", - "type": "blob", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdAt": { - "name": "createdAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updatedAt": { - "name": "updatedAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "jobs_userId_idx": { - "name": "jobs_userId_idx", - "columns": ["userId"], - "isUnique": false - }, - "jobs_status_idx": { - "name": "jobs_status_idx", - "columns": ["status"], - "isUnique": false - }, - "jobs_createdAt_idx": { - "name": "jobs_createdAt_idx", - "columns": ["createdAt"], - "isUnique": false - }, - "jobs_url_idx": { - "name": "jobs_url_idx", - "columns": ["url"], - "isUnique": false - } - }, - "foreignKeys": { - "jobs_userId_user_id_fk": { - "name": "jobs_userId_user_id_fk", - "tableFrom": "jobs", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "space": { - "name": "space", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'none'" - }, - "user": { - "name": "user", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdAt": { - "name": "createdAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "numItems": { - "name": "numItems", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "space_name_unique": { - "name": "space_name_unique", - "columns": ["name"], - "isUnique": true - }, - "spaces_name_idx": { - "name": "spaces_name_idx", - "columns": ["name"], - "isUnique": false - }, - "spaces_user_idx": { - "name": "spaces_user_idx", - "columns": ["user"], - "isUnique": false - } - }, - "foreignKeys": { - "space_user_user_id_fk": { - "name": "space_user_user_id_fk", - "tableFrom": "space", - "tableTo": "user", - "columnsFrom": ["user"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "spacesAccess": { - "name": "spacesAccess", - "columns": { - "spaceId": { - "name": "spaceId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "spacesAccess_spaceId_space_id_fk": { - "name": "spacesAccess_spaceId_space_id_fk", - "tableFrom": "spacesAccess", - "tableTo": "space", - "columnsFrom": ["spaceId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "spacesAccess_spaceId_userEmail_pk": { - "columns": ["spaceId", "userEmail"], - "name": "spacesAccess_spaceId_userEmail_pk" - } - }, - "uniqueConstraints": {} - }, - "storedContent": { - "name": "storedContent", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "savedAt": { - "name": "savedAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "baseUrl": { - "name": "baseUrl", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "ogImage": { - "name": "ogImage", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'page'" - }, - "image": { - "name": "image", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user": { - "name": "user", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "noteId": { - "name": "noteId", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "storedContent_baseUrl_unique": { - "name": "storedContent_baseUrl_unique", - "columns": ["baseUrl"], - "isUnique": true - }, - "storedContent_url_idx": { - "name": "storedContent_url_idx", - "columns": ["url"], - "isUnique": false - }, - "storedContent_savedAt_idx": { - "name": "storedContent_savedAt_idx", - "columns": ["savedAt"], - "isUnique": false - }, - "storedContent_title_idx": { - "name": "storedContent_title_idx", - "columns": ["title"], - "isUnique": false - }, - "storedContent_user_idx": { - "name": "storedContent_user_idx", - "columns": ["user"], - "isUnique": false - } - }, - "foreignKeys": { - "storedContent_user_user_id_fk": { - "name": "storedContent_user_user_id_fk", - "tableFrom": "storedContent", - "tableTo": "user", - "columnsFrom": ["user"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "telegramId": { - "name": "telegramId", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "hasOnboarded": { - "name": "hasOnboarded", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": false - } - }, - "indexes": { - "users_email_idx": { - "name": "users_email_idx", - "columns": ["email"], - "isUnique": false - }, - "users_telegram_idx": { - "name": "users_telegram_idx", - "columns": ["telegramId"], - "isUnique": false - }, - "users_id_idx": { - "name": "users_id_idx", - "columns": ["id"], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "verificationToken": { - "name": "verificationToken", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationToken_identifier_token_pk": { - "columns": ["identifier", "token"], - "name": "verificationToken_identifier_token_pk" - } - }, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - } -} + "version": "6", + "dialect": "sqlite", + "id": "0a45f2f7-9565-44be-8c30-71edebcc4e41", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {} + }, + "authenticator": { + "name": "authenticator", + "columns": { + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "authenticator_credentialID_unique": { + "name": "authenticator_credentialID_unique", + "columns": [ + "credentialID" + ], + "isUnique": true + } + }, + "foreignKeys": { + "authenticator_userId_user_id_fk": { + "name": "authenticator_userId_user_id_fk", + "tableFrom": "authenticator", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "authenticator_userId_credentialID_pk": { + "columns": [ + "credentialID", + "userId" + ], + "name": "authenticator_userId_credentialID_pk" + } + }, + "uniqueConstraints": {} + }, + "canvas": { + "name": "canvas", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Untitled'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Untitled'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "canvas_user_userId": { + "name": "canvas_user_userId", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "canvas_userId_user_id_fk": { + "name": "canvas_userId_user_id_fk", + "tableFrom": "canvas", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "chatHistory": { + "name": "chatHistory", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "threadId": { + "name": "threadId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answerParts": { + "name": "answerParts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "answerSources": { + "name": "answerSources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "answerJustification": { + "name": "answerJustification", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2024-09-05T18:04:25.816Z\"'" + } + }, + "indexes": { + "chatHistory_thread_idx": { + "name": "chatHistory_thread_idx", + "columns": [ + "threadId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chatHistory_threadId_chatThread_id_fk": { + "name": "chatHistory_threadId_chatThread_id_fk", + "tableFrom": "chatHistory", + "tableTo": "chatThread", + "columnsFrom": [ + "threadId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "chatThread": { + "name": "chatThread", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "firstMessage": { + "name": "firstMessage", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "chatThread_user_idx": { + "name": "chatThread_user_idx", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chatThread_userId_user_id_fk": { + "name": "chatThread_userId_user_id_fk", + "tableFrom": "chatThread", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "contentToSpace": { + "name": "contentToSpace", + "columns": { + "contentId": { + "name": "contentId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "spaceId": { + "name": "spaceId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "contentToSpace_contentId_storedContent_id_fk": { + "name": "contentToSpace_contentId_storedContent_id_fk", + "tableFrom": "contentToSpace", + "tableTo": "storedContent", + "columnsFrom": [ + "contentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "contentToSpace_spaceId_space_id_fk": { + "name": "contentToSpace_spaceId_space_id_fk", + "tableFrom": "contentToSpace", + "tableTo": "space", + "columnsFrom": [ + "spaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "contentToSpace_contentId_spaceId_pk": { + "columns": [ + "contentId", + "spaceId" + ], + "name": "contentToSpace_contentId_spaceId_pk" + } + }, + "uniqueConstraints": {} + }, + "jobs": { + "name": "jobs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "lastAttemptAt": { + "name": "lastAttemptAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2024-09-05T18:04:25.816Z\"'" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2024-09-05T18:04:25.816Z\"'" + } + }, + "indexes": { + "jobs_userId_idx": { + "name": "jobs_userId_idx", + "columns": [ + "userId" + ], + "isUnique": false + }, + "jobs_status_idx": { + "name": "jobs_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "jobs_createdAt_idx": { + "name": "jobs_createdAt_idx", + "columns": [ + "createdAt" + ], + "isUnique": false + }, + "jobs_url_idx": { + "name": "jobs_url_idx", + "columns": [ + "url" + ], + "isUnique": false + } + }, + "foreignKeys": { + "jobs_userId_user_id_fk": { + "name": "jobs_userId_user_id_fk", + "tableFrom": "jobs", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "space": { + "name": "space", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "user": { + "name": "user", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "numItems": { + "name": "numItems", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "space_name_unique": { + "name": "space_name_unique", + "columns": [ + "name" + ], + "isUnique": true + }, + "spaces_name_idx": { + "name": "spaces_name_idx", + "columns": [ + "name" + ], + "isUnique": false + }, + "spaces_user_idx": { + "name": "spaces_user_idx", + "columns": [ + "user" + ], + "isUnique": false + } + }, + "foreignKeys": { + "space_user_user_id_fk": { + "name": "space_user_user_id_fk", + "tableFrom": "space", + "tableTo": "user", + "columnsFrom": [ + "user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "spacesAccess": { + "name": "spacesAccess", + "columns": { + "spaceId": { + "name": "spaceId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userEmail": { + "name": "userEmail", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "spacesAccess_spaceId_space_id_fk": { + "name": "spacesAccess_spaceId_space_id_fk", + "tableFrom": "spacesAccess", + "tableTo": "space", + "columnsFrom": [ + "spaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "spacesAccess_spaceId_userEmail_pk": { + "columns": [ + "spaceId", + "userEmail" + ], + "name": "spacesAccess_spaceId_userEmail_pk" + } + }, + "uniqueConstraints": {} + }, + "storedContent": { + "name": "storedContent", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "baseUrl": { + "name": "baseUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ogImage": { + "name": "ogImage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'page'" + }, + "image": { + "name": "image", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user": { + "name": "user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "noteId": { + "name": "noteId", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "storedContent_url_idx": { + "name": "storedContent_url_idx", + "columns": [ + "url" + ], + "isUnique": false + }, + "storedContent_savedAt_idx": { + "name": "storedContent_savedAt_idx", + "columns": [ + "savedAt" + ], + "isUnique": false + }, + "storedContent_title_idx": { + "name": "storedContent_title_idx", + "columns": [ + "title" + ], + "isUnique": false + }, + "storedContent_user_idx": { + "name": "storedContent_user_idx", + "columns": [ + "user" + ], + "isUnique": false + } + }, + "foreignKeys": { + "storedContent_user_user_id_fk": { + "name": "storedContent_user_user_id_fk", + "tableFrom": "storedContent", + "tableTo": "user", + "columnsFrom": [ + "user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hasOnboarded": { + "name": "hasOnboarded", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_telegram_idx": { + "name": "users_telegram_idx", + "columns": [ + "telegramId" + ], + "isUnique": false + }, + "users_id_idx": { + "name": "users_id_idx", + "columns": [ + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": [ + "identifier", + "token" + ], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/apps/web/migrations/meta/0001_snapshot.json b/apps/web/migrations/meta/0001_snapshot.json deleted file mode 100644 index cc157c33..00000000 --- a/apps/web/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,923 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "30bdc8fa-70a7-44b7-a06b-59b950a62c11", - "prevId": "3fbdb153-2764-4b09-ac22-05c3a131ec35", - "tables": { - "account": { - "name": "account", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "account_provider_providerAccountId_pk": { - "columns": ["provider", "providerAccountId"], - "name": "account_provider_providerAccountId_pk" - } - }, - "uniqueConstraints": {} - }, - "authenticator": { - "name": "authenticator", - "columns": { - "credentialID": { - "name": "credentialID", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "credentialPublicKey": { - "name": "credentialPublicKey", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "counter": { - "name": "counter", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "credentialDeviceType": { - "name": "credentialDeviceType", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "credentialBackedUp": { - "name": "credentialBackedUp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "transports": { - "name": "transports", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "authenticator_credentialID_unique": { - "name": "authenticator_credentialID_unique", - "columns": ["credentialID"], - "isUnique": true - } - }, - "foreignKeys": { - "authenticator_userId_user_id_fk": { - "name": "authenticator_userId_user_id_fk", - "tableFrom": "authenticator", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "authenticator_userId_credentialID_pk": { - "columns": ["credentialID", "userId"], - "name": "authenticator_userId_credentialID_pk" - } - }, - "uniqueConstraints": {} - }, - "canvas": { - "name": "canvas", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'Untitled'" - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'Untitled'" - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "''" - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "canvas_user_userId": { - "name": "canvas_user_userId", - "columns": ["userId"], - "isUnique": false - } - }, - "foreignKeys": { - "canvas_userId_user_id_fk": { - "name": "canvas_userId_user_id_fk", - "tableFrom": "canvas", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "chatHistory": { - "name": "chatHistory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "threadId": { - "name": "threadId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "question": { - "name": "question", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answerParts": { - "name": "answerParts", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "answerSources": { - "name": "answerSources", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "answerJustification": { - "name": "answerJustification", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdAt": { - "name": "createdAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2024-08-27T18:12:22.062Z\"'" - } - }, - "indexes": { - "chatHistory_thread_idx": { - "name": "chatHistory_thread_idx", - "columns": ["threadId"], - "isUnique": false - } - }, - "foreignKeys": { - "chatHistory_threadId_chatThread_id_fk": { - "name": "chatHistory_threadId_chatThread_id_fk", - "tableFrom": "chatHistory", - "tableTo": "chatThread", - "columnsFrom": ["threadId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "chatThread": { - "name": "chatThread", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "firstMessage": { - "name": "firstMessage", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "chatThread_user_idx": { - "name": "chatThread_user_idx", - "columns": ["userId"], - "isUnique": false - } - }, - "foreignKeys": { - "chatThread_userId_user_id_fk": { - "name": "chatThread_userId_user_id_fk", - "tableFrom": "chatThread", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "contentToSpace": { - "name": "contentToSpace", - "columns": { - "contentId": { - "name": "contentId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "spaceId": { - "name": "spaceId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "contentToSpace_contentId_storedContent_id_fk": { - "name": "contentToSpace_contentId_storedContent_id_fk", - "tableFrom": "contentToSpace", - "tableTo": "storedContent", - "columnsFrom": ["contentId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "contentToSpace_spaceId_space_id_fk": { - "name": "contentToSpace_spaceId_space_id_fk", - "tableFrom": "contentToSpace", - "tableTo": "space", - "columnsFrom": ["spaceId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "contentToSpace_contentId_spaceId_pk": { - "columns": ["contentId", "spaceId"], - "name": "contentToSpace_contentId_spaceId_pk" - } - }, - "uniqueConstraints": {} - }, - "jobs": { - "name": "jobs", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "lastAttemptAt": { - "name": "lastAttemptAt", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "error": { - "name": "error", - "type": "blob", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdAt": { - "name": "createdAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2024-08-27T18:12:22.062Z\"'" - }, - "updatedAt": { - "name": "updatedAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2024-08-27T18:12:22.062Z\"'" - } - }, - "indexes": { - "jobs_userId_idx": { - "name": "jobs_userId_idx", - "columns": ["userId"], - "isUnique": false - }, - "jobs_status_idx": { - "name": "jobs_status_idx", - "columns": ["status"], - "isUnique": false - }, - "jobs_createdAt_idx": { - "name": "jobs_createdAt_idx", - "columns": ["createdAt"], - "isUnique": false - }, - "jobs_url_idx": { - "name": "jobs_url_idx", - "columns": ["url"], - "isUnique": false - } - }, - "foreignKeys": { - "jobs_userId_user_id_fk": { - "name": "jobs_userId_user_id_fk", - "tableFrom": "jobs", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "space": { - "name": "space", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'none'" - }, - "user": { - "name": "user", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdAt": { - "name": "createdAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "numItems": { - "name": "numItems", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "space_name_unique": { - "name": "space_name_unique", - "columns": ["name"], - "isUnique": true - }, - "spaces_name_idx": { - "name": "spaces_name_idx", - "columns": ["name"], - "isUnique": false - }, - "spaces_user_idx": { - "name": "spaces_user_idx", - "columns": ["user"], - "isUnique": false - } - }, - "foreignKeys": { - "space_user_user_id_fk": { - "name": "space_user_user_id_fk", - "tableFrom": "space", - "tableTo": "user", - "columnsFrom": ["user"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "spacesAccess": { - "name": "spacesAccess", - "columns": { - "spaceId": { - "name": "spaceId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "spacesAccess_spaceId_space_id_fk": { - "name": "spacesAccess_spaceId_space_id_fk", - "tableFrom": "spacesAccess", - "tableTo": "space", - "columnsFrom": ["spaceId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "spacesAccess_spaceId_userEmail_pk": { - "columns": ["spaceId", "userEmail"], - "name": "spacesAccess_spaceId_userEmail_pk" - } - }, - "uniqueConstraints": {} - }, - "storedContent": { - "name": "storedContent", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "savedAt": { - "name": "savedAt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "baseUrl": { - "name": "baseUrl", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "ogImage": { - "name": "ogImage", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'page'" - }, - "image": { - "name": "image", - "type": "text(255)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user": { - "name": "user", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "noteId": { - "name": "noteId", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "storedContent_url_idx": { - "name": "storedContent_url_idx", - "columns": ["url"], - "isUnique": false - }, - "storedContent_savedAt_idx": { - "name": "storedContent_savedAt_idx", - "columns": ["savedAt"], - "isUnique": false - }, - "storedContent_title_idx": { - "name": "storedContent_title_idx", - "columns": ["title"], - "isUnique": false - }, - "storedContent_user_idx": { - "name": "storedContent_user_idx", - "columns": ["user"], - "isUnique": false - } - }, - "foreignKeys": { - "storedContent_user_user_id_fk": { - "name": "storedContent_user_user_id_fk", - "tableFrom": "storedContent", - "tableTo": "user", - "columnsFrom": ["user"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "telegramId": { - "name": "telegramId", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "hasOnboarded": { - "name": "hasOnboarded", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": false - } - }, - "indexes": { - "users_email_idx": { - "name": "users_email_idx", - "columns": ["email"], - "isUnique": false - }, - "users_telegram_idx": { - "name": "users_telegram_idx", - "columns": ["telegramId"], - "isUnique": false - }, - "users_id_idx": { - "name": "users_id_idx", - "columns": ["id"], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "verificationToken": { - "name": "verificationToken", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires": { - "name": "expires", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "verificationToken_identifier_token_pk": { - "columns": ["identifier", "token"], - "name": "verificationToken_identifier_token_pk" - } - }, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - } -} diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json index 03637d1d..5fdad8aa 100644 --- a/apps/web/migrations/meta/_journal.json +++ b/apps/web/migrations/meta/_journal.json @@ -1,20 +1,13 @@ { - "version": "6", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1722411353835, - "tag": "0000_fixed_pandemic", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1724782342070, - "tag": "0001_huge_puma", - "breakpoints": true - } - ] -} + "version": "6", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1725559465821, + "tag": "0000_adorable_iron_man", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/web/server/encrypt.ts b/apps/web/server/encrypt.ts index d2bbf4d3..db0999a1 100644 --- a/apps/web/server/encrypt.ts +++ b/apps/web/server/encrypt.ts @@ -1,6 +1,6 @@ function convertStringToFixedNumber(input: string): number { let hash = 0; - for (let i = 0; i < input.length; i++) { + for (let i = 0; i < input?.length; i++) { const char = input.charCodeAt(i); hash = (hash * 31 + char) % 1000000007; // Hashing by a large prime number } @@ -19,7 +19,7 @@ function random(seed: number) { } function shuffle(array: string[], seed: number) { - let m = array.length, + let m = array?.length, t, i; @@ -38,7 +38,7 @@ function shuffle(array: string[], seed: number) { export const cipher = (text: string) => { let returned_text = ""; - for (let i = 0; i < text.length; i++) { + for (let i = 0; i < text?.length; i++) { returned_text += shuffled[chars.indexOf(text[i]!)]; } @@ -49,19 +49,19 @@ export const decipher = (text: string) => { let returned_text = ""; const index = Math.floor( random(convertStringToFixedNumber(process.env.BACKEND_SECURITY_KEY)) * - (text.length / 2), + (text?.length / 2), ); - for (let i = 0; i < text.length; i++) { + for (let i = 0; i < text?.length; i++) { returned_text += chars[shuffled.indexOf(text[i]!)]; } const total = parseInt(text[index]!); const str = parseInt(text.slice(index + 1, index + total + 1)); - return returned_text.slice(text.length - str); + return returned_text.slice(text?.length - str); }; const extend = (text: string, length = 60) => { - const extra = length - text.length; + const extra = length - text?.length; if (extra < 0) { return text; @@ -74,9 +74,9 @@ const extend = (text: string, length = 60) => { ); const storage_string = - text.length.toString().length.toString() + text.length.toString(); + text?.length.toString()?.length.toString() + text?.length.toString(); let returned = ""; - let total = storage_string.length + text.length; + let total = storage_string?.length + text?.length; for (let i = 0; i < extra; i++) { if (i == index) { @@ -86,7 +86,8 @@ const extend = (text: string, length = 60) => { break; } // Add a random character - returned += shuffled[Math.floor(random(Math.random()) * shuffled.length)]; + returned += + shuffled[Math.floor(random(Math.random()) * shuffled?.length)]; total++; } }