aboutsummaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorcodetorso <[email protected]>2024-07-19 01:33:25 +0530
committercodetorso <[email protected]>2024-07-19 01:33:25 +0530
commit9c4a36463239a355cdbfdd3fbb82ac8be4504ec6 (patch)
tree563d6056daa38e3f40869126c5d22e263bda423f /apps/web
parenteffect of disappearing text on input (diff)
downloadsupermemory-9c4a36463239a355cdbfdd3fbb82ac8be4504ec6.tar.xz
supermemory-9c4a36463239a355cdbfdd3fbb82ac8be4504ec6.zip
recent searches feature and some other cool stuff
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/app/(dash)/home/filterSpaces.tsx37
-rw-r--r--apps/web/app/(dash)/home/heading.tsx28
-rw-r--r--apps/web/app/(dash)/home/page.tsx56
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx4
-rw-r--r--apps/web/app/actions/fetchers.ts30
-rw-r--r--apps/web/migrations/0001_nervous_longshot.sql2
-rw-r--r--apps/web/migrations/meta/0001_snapshot.json905
-rw-r--r--apps/web/migrations/meta/_journal.json31
-rw-r--r--apps/web/package.json1
-rw-r--r--apps/web/server/db/schema.ts3
10 files changed, 1045 insertions, 52 deletions
diff --git a/apps/web/app/(dash)/home/filterSpaces.tsx b/apps/web/app/(dash)/home/filterSpaces.tsx
index d0e4fa1e..061923d8 100644
--- a/apps/web/app/(dash)/home/filterSpaces.tsx
+++ b/apps/web/app/(dash)/home/filterSpaces.tsx
@@ -76,30 +76,31 @@ export function FilterSpaces({
</div>
<CommandList className="z-10 translate-y-12 translate-x-5 opacity-0 absolute group-focus-within:opacity-100 transition-opacity p-2 rounded-lg max-w-64 bg-[#2C3338]">
<CommandGroup className="pointer-events-none opacity-0 group-focus-within:opacity-100 scale-50 scale-y-50 group-focus-within:scale-y-100 group-focus-within:scale-100 group-focus-within:pointer-events-auto transition-all origin-top">
- {initialSpaces.map((space) => (
- <CommandItem
- className="text-[#eaeaea] data-[disabled]:opacity-90"
- value={space.name}
- key={space.id}
- onSelect={() => handleSelect(space)}
- >
- <Check
- className={`mr-2 h-4 w-4 ${selectedSpaces.some((v) => v.id === space.id) ? "opacity-100" : "opacity-0"}`}
- />
- {space.name}
- </CommandItem>
- ))}
+ {initialSpaces.map((space) => {
+ if (!selectedSpaces.some((v) => v.id === space.id)) {
+ return (
+ <CommandItem
+ className="text-[#eaeaea] data-[disabled]:opacity-90"
+ value={space.name}
+ key={space.id}
+ onSelect={() => handleSelect(space)}
+ >
+ <Check
+ className={`mr-2 h-4 w-4 ${selectedSpaces.some((v) => v.id === space.id) ? "opacity-100" : "opacity-0"}`}
+ />
+ {space.name}
+ </CommandItem>
+ );
+ }
+ })}
</CommandGroup>
</CommandList>
</Command>
</div>
- {/* <button
+ <button
type="submit"
- className="h-12 w-12 rounded-[14px] all-center shrink-0 hover:brightness-125 outline-none bg-[#369DFD1A] p-3 active:scale-90"
+ className="rounded-lg bg-[#369DFD1A] p-3 transition-colors"
>
- <Image src={ArrowRightIcon} alt="Right arrow icon" />
- </button> */}
- <button type="submit" className="rounded-lg bg-[#369DFD1A] p-3 transition-colors">
<Image src={ArrowRightIcon} alt="Enter" />
</button>
</div>
diff --git a/apps/web/app/(dash)/home/heading.tsx b/apps/web/app/(dash)/home/heading.tsx
index 75a83dae..e50f509b 100644
--- a/apps/web/app/(dash)/home/heading.tsx
+++ b/apps/web/app/(dash)/home/heading.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { motion } from "framer-motion";
+import { AnimatePresence, motion } from "framer-motion";
const headings = [
"Unlock your digital brain",
@@ -13,17 +13,23 @@ export function Heading({ queryPresent }: { queryPresent: boolean }) {
const [showHeading, setShowHeading] = useState<number>(0);
useEffect(() => {
setShowHeading(Math.floor(Math.random() * headings.length));
- }, []);
+ }, [queryPresent]);
return (
- <div className="h-[3.4rem] overflow-hidden text-white text-center">
- <motion.h1
- animate={{ opacity: queryPresent ? 0 : 1, y: queryPresent ? "20%" : 0 }}
- className={`text-[2.45rem] font-semibold ${
- queryPresent ? "pointer-events-none" : "pointer-events-auto"
- } transition-opacity`}
- >
- {headings[showHeading]}
- </motion.h1>
+ <div className="h-[7rem] flex items-end justify-center overflow-hidden text-white">
+ <AnimatePresence mode="popLayout">
+ {!queryPresent && (
+ <motion.h1
+ initial={{ opacity: 0, y: "20%" }}
+ animate={{ opacity: 1, y: "0%" }}
+ exit={{ opacity: 0, y: "20%" }}
+ className={`text-[2.45rem] font-semibold ${
+ queryPresent ? "pointer-events-none" : "pointer-events-auto"
+ } transition-opacity`}
+ >
+ {headings[showHeading]}
+ </motion.h1>
+ )}
+ </AnimatePresence>
</div>
);
}
diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx
index 84ecf242..7572056a 100644
--- a/apps/web/app/(dash)/home/page.tsx
+++ b/apps/web/app/(dash)/home/page.tsx
@@ -1,12 +1,18 @@
"use client";
-import React, { useEffect, useState } from "react";
+import React, { Suspense, memo, use, useEffect, useState } from "react";
import QueryInput from "./queryinput";
-import { getSessionAuthToken, getSpaces } from "@/app/actions/fetchers";
+import {
+ getRecentChats,
+ getSessionAuthToken,
+ getSpaces,
+} from "@/app/actions/fetchers";
import { useRouter } from "next/navigation";
import { createChatThread, linkTelegramToUser } from "@/app/actions/doers";
import { toast } from "sonner";
import { Heading } from "./heading";
+import { ArrowLongRightIcon } from "@heroicons/react/24/outline";
+import Link from "next/link";
const linkTelegram = async (telegramUser: string) => {
const response = await linkTelegramToUser(telegramUser);
@@ -23,7 +29,6 @@ function Page({
}: {
searchParams: Record<string, string | string[] | undefined>;
}) {
-
const { push } = useRouter();
const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]);
@@ -32,7 +37,7 @@ function Page({
useEffect(() => {
// telegram bot
- const telegramUser = searchParams.extension as string
+ const telegramUser = searchParams.extension as string;
if (telegramUser) {
linkTelegram(telegramUser);
}
@@ -57,11 +62,11 @@ function Page({
}, []);
return (
- <div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col px-2 md:px-0">
+ <div className="max-w-3xl mt-[18vh] mx-auto w-full px-2 md:px-0">
<Heading queryPresent={queryPresent} />
- <div className="w-full pb-20 mt-12">
+ <div className="w-full py-12">
<QueryInput
- setQueryPresent={(t:boolean)=> setQueryPresent(t)}
+ setQueryPresent={(t: boolean) => setQueryPresent(t)}
handleSubmit={async (q, spaces) => {
if (q.length === 0) {
toast.error("Query is required");
@@ -82,8 +87,45 @@ function Page({
initialSpaces={spaces}
/>
</div>
+ <History />
</div>
);
}
+const History = memo(() => {
+ const [chatThreads, setChatThreads] = useState(null);
+
+ useEffect(() => {
+ (async () => {
+ const chatThreads = await getRecentChats();
+
+ setChatThreads(chatThreads);
+ })();
+ }, []);
+
+ if (!chatThreads){
+ return <div>Loading</div>;
+ }
+
+ if (!chatThreads.success || !chatThreads.data) {
+ return <div>Error fetching chat threads</div>;
+ }
+
+ return (
+ <div className="space-y-5">
+ <h3 className="text-lg">Recent Searches</h3>
+ <ul className="text-base list-none space-y-3 text-[#b9b9b9]">
+ {chatThreads.data.map((thread) => (
+ <li className="flex items-center gap-2 truncate">
+ <ArrowLongRightIcon className="h-5" />{" "}
+ <Link prefetch={false} href={`/chat/${thread.id}`}>
+ {thread.firstMessage}
+ </Link>
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+});
+
export default Page;
diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx
index 6767d5ea..930f34db 100644
--- a/apps/web/app/(dash)/home/queryinput.tsx
+++ b/apps/web/app/(dash)/home/queryinput.tsx
@@ -25,9 +25,9 @@ function QueryInput({
>([]);
return (
- <div className={`${className}`}>
+ <div className={`w-full`}>
<div
- className={`bg-[#1F2428] overflow-hidden border-2 border-gray-700/50 shadow-md shadow-[#1d1d1dc7] rounded-3xl`}
+ className={`bg-[#1F2428] overflow-hidden border-2 border-gray-700/50 shadow-md shadow-[#1d1d1dc7] rounded-3xl`}
>
{/* input and action button */}
<form
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
index 1838ee1c..74df0b04 100644
--- a/apps/web/app/actions/fetchers.ts
+++ b/apps/web/app/actions/fetchers.ts
@@ -1,6 +1,6 @@
"use server";
-import { and, asc, eq, exists, inArray, not, or, sql } from "drizzle-orm";
+import { and, asc, desc, eq, exists, inArray, not, or, sql } from "drizzle-orm";
import { db } from "../../server/db";
import {
canvas,
@@ -228,6 +228,34 @@ export const getFullChatThread = async (
};
};
+export const getRecentChats = async (): ServerActionReturnType<ChatThread[]> => {
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ redirect("/signin");
+ return { error: "Not authenticated", success: false };
+ }
+
+ try {
+ const chatHistorys = await db.query.chatThreads.findMany({
+ where: eq(chatThreads.userId, data.user.id),
+ orderBy: desc(chatThreads.createdAt),
+ limit: 4,
+ });
+
+ return {
+ success: true,
+ data: chatHistorys,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ error: (e as Error).message,
+ };
+ }
+};
+
+
export const getChatHistory = async (): ServerActionReturnType<
ChatThread[]
> => {
diff --git a/apps/web/migrations/0001_nervous_longshot.sql b/apps/web/migrations/0001_nervous_longshot.sql
new file mode 100644
index 00000000..a15917c6
--- /dev/null
+++ b/apps/web/migrations/0001_nervous_longshot.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `chatThread` ADD `createdAt` integer;
+UPDATE `chatThread` SET `createdAt` = strftime('%s', 'now') WHERE `createdAt` IS NULL;
diff --git a/apps/web/migrations/meta/0001_snapshot.json b/apps/web/migrations/meta/0001_snapshot.json
new file mode 100644
index 00000000..91c50c13
--- /dev/null
+++ b/apps/web/migrations/meta/0001_snapshot.json
@@ -0,0 +1,905 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "39768c98-95d2-4d42-bae0-43e3e511be7e",
+ "prevId": "ab91d972-05ff-4916-84b7-1cfaab4c3879",
+ "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
+ }
+ },
+ "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
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "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": {}
+ },
+ "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
+ }
+ },
+ "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": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json
index c7ab51e1..7af2b032 100644
--- a/apps/web/migrations/meta/_journal.json
+++ b/apps/web/migrations/meta/_journal.json
@@ -1,13 +1,20 @@
{
- "version": "6",
- "dialect": "sqlite",
- "entries": [
- {
- "idx": 0,
- "version": "6",
- "when": 1720360287793,
- "tag": "0000_exotic_sway",
- "breakpoints": true
- }
- ]
-}
+ "version": "6",
+ "dialect": "sqlite",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "6",
+ "when": 1720360287793,
+ "tag": "0000_exotic_sway",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "6",
+ "when": 1721329604476,
+ "tag": "0001_nervous_longshot",
+ "breakpoints": true
+ }
+ ]
+} \ No newline at end of file
diff --git a/apps/web/package.json b/apps/web/package.json
index ed3eec11..3699d02d 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -23,6 +23,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@sentry/nextjs": "^8",
"cmdk": "^1.0.0",
+ "drizzle-kit": "^0.23.0",
"lowlight": "^3.1.0",
"million": "^3.1.6",
"next": "^14.1.1",
diff --git a/apps/web/server/db/schema.ts b/apps/web/server/db/schema.ts
index ae293a91..8d6157e9 100644
--- a/apps/web/server/db/schema.ts
+++ b/apps/web/server/db/schema.ts
@@ -193,7 +193,8 @@ export const chatThreads = createTable(
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
- },
+ createdAt: integer("createdAt").notNull().$defaultFn(() => Math.floor(Date.now() / 1000)), // Use Unix timestamp
+ },
(thread) => ({
userIdx: index("chatThread_user_idx").on(thread.userId),
}),