aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-06-29 09:34:28 -0500
committerDhravya <[email protected]>2024-06-29 09:34:28 -0500
commitf420dc7616b2e66564a23881a9a2aec7f34df4e0 (patch)
treebca7fb126a9aa0a2a945a4b53ff0ce34c0d3d429 /apps
parentmore tweaks (diff)
downloadsupermemory-f420dc7616b2e66564a23881a9a2aec7f34df4e0.tar.xz
supermemory-f420dc7616b2e66564a23881a9a2aec7f34df4e0.zip
schema
Diffstat (limited to 'apps')
-rw-r--r--apps/web/app/(dash)/dynamicisland.tsx315
-rw-r--r--apps/web/app/(dash)/layout.tsx2
-rw-r--r--apps/web/app/(dash)/memories/page.tsx93
-rw-r--r--apps/web/app/(dash)/menu.tsx5
-rw-r--r--apps/web/app/actions/fetchers.ts11
-rw-r--r--apps/web/app/actions/types.ts1
-rw-r--r--apps/web/migrations/0000_classy_speed_demon.sql113
-rw-r--r--apps/web/migrations/meta/0000_snapshot.json685
-rw-r--r--apps/web/migrations/meta/_journal.json13
-rw-r--r--apps/web/server/db/schema.ts1
10 files changed, 873 insertions, 366 deletions
diff --git a/apps/web/app/(dash)/dynamicisland.tsx b/apps/web/app/(dash)/dynamicisland.tsx
deleted file mode 100644
index 8b1b4633..00000000
--- a/apps/web/app/(dash)/dynamicisland.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-"use client";
-
-import { AddIcon } from "@repo/ui/icons";
-import Image from "next/image";
-
-import { AnimatePresence, useMotionValueEvent, useScroll } from "framer-motion";
-import { useActionState, useEffect, useRef, useState } from "react";
-import { motion } from "framer-motion";
-import { Label } from "@repo/ui/shadcn/label";
-import { Input } from "@repo/ui/shadcn/input";
-import { Textarea } from "@repo/ui/shadcn/textarea";
-import { createMemory, createSpace } from "../actions/doers";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@repo/ui/shadcn/select";
-import { Space } from "../actions/types";
-import { getSpaces } from "../actions/fetchers";
-import { toast } from "sonner";
-import { useFormStatus } from "react-dom";
-
-export function DynamicIsland() {
- const { scrollYProgress } = useScroll();
- const [visible, setVisible] = useState(true);
-
- useMotionValueEvent(scrollYProgress, "change", (current) => {
- if (typeof current === "number") {
- let direction = current! - scrollYProgress.getPrevious()!;
-
- if (direction < 0 || direction === 1) {
- setVisible(true);
- } else {
- setVisible(false);
- }
- }
- });
-
- return (
- <div className="">
- <AnimatePresence mode="wait">
- <motion.div
- initial={{
- opacity: 1,
- y: -150,
- }}
- animate={{
- y: visible ? 0 : -150,
- opacity: visible ? 1 : 0,
- }}
- transition={{
- duration: 0.2,
- }}
- className="flex flex-col items-center"
- >
- <DynamicIslandContent />
- </motion.div>
- </AnimatePresence>
- </div>
- );
-}
-
-export default DynamicIsland;
-
-function DynamicIslandContent() {
- const [show, setshow] = useState(true);
- function cancelfn() {
- setshow(true);
- }
-
- const lastBtn = useRef<string>();
-
- useEffect(() => {
- document.addEventListener("keydown", (e) => {
- if (e.key === "Escape") {
- setshow(true);
- }
-
- if (e.key === "a" && lastBtn.current === "Alt") {
- setshow(false);
- }
- lastBtn.current = e.key;
- });
- }, []);
- return (
- <>
- {show ? (
- <button
- onClick={() => setshow(!show)}
- className="bg-secondary p-2 text-[#989EA4] rounded-full flex items-center justify-between gap-2 px-4 h-10 pr-5 z-[999] shadow-md"
- >
- <Image src={AddIcon} alt="add icon" />
- Add content
- </button>
- ) : (
- <ToolBar cancelfn={cancelfn} />
- )}
- </>
- );
-}
-
-const fakeitems = ["page", "spaces"];
-
-function ToolBar({ cancelfn }: { cancelfn: () => void }) {
- const [spaces, setSpaces] = useState<Space[]>([]);
-
- const [index, setIndex] = useState(0);
-
- useEffect(() => {
- (async () => {
- let spaces = await getSpaces();
-
- if (!spaces.success || !spaces.data) {
- toast.warning("Unable to get spaces", {
- richColors: true,
- });
- setSpaces([]);
- return;
- }
- setSpaces(spaces.data);
- })();
- }, []);
-
- return (
- <AnimatePresence mode="wait">
- <motion.div
- initial={{
- opacity: 0,
- y: 20,
- }}
- animate={{
- y: 0,
- opacity: 1,
- }}
- exit={{
- opacity: 0,
- y: 20,
- }}
- transition={{
- duration: 0.2,
- }}
- className="flex flex-col items-center"
- >
- <div className="bg-secondary py-[.35rem] px-[.6rem] rounded-2xl">
- <HoverEffect
- items={fakeitems}
- index={index}
- indexFn={(i) => setIndex(i)}
- />
- </div>
- {index === 1 ? (
- <SpaceForm cancelfn={cancelfn} />
- ) : (
- <PageForm cancelfn={cancelfn} spaces={spaces} />
- )}
- </motion.div>
- </AnimatePresence>
- );
-}
-
-export const HoverEffect = ({
- items,
- index,
- indexFn,
-}: {
- items: string[];
- index: number;
- indexFn: (i: number) => void;
-}) => {
- return (
- <div className={"flex"}>
- {items.map((item, idx) => (
- <button
- key={idx}
- className="relative block h-full w-full px-2 py-1"
- onClick={() => indexFn(idx)}
- >
- <AnimatePresence>
- {index === idx && (
- <motion.span
- className="absolute inset-0 block h-full w-full rounded-xl bg-[#2B3237]"
- layoutId="hoverBackground"
- initial={{ opacity: 0 }}
- animate={{
- opacity: 1,
- transition: { duration: 0.15 },
- }}
- exit={{
- opacity: 0,
- transition: { duration: 0.15, delay: 0.2 },
- }}
- />
- )}
- </AnimatePresence>
- <h3 className="text-[#858B92] z-50 relative">{item}</h3>
- </button>
- ))}
- </div>
- );
-};
-
-function SpaceForm({ cancelfn }: { cancelfn: () => void }) {
- return (
- <form
- action={createSpace}
- className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3"
- >
- <div>
- <Label className="text-[#858B92]" htmlFor="name">
- Name
- </Label>
- <Input
- className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
- id="name"
- name="name"
- />
- </div>
- <div className="flex justify-between">
- {/* <a className="text-blue-500" href="">
- pull from store
- </a> */}
- {/* <div
- onClick={cancelfn}
- className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
- >
- cancel
- </div> */}
- <button
- type="submit"
- className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
- >
- Submit
- </button>
- </div>
- </form>
- );
-}
-
-function PageForm({
- cancelfn,
- spaces,
-}: {
- cancelfn: () => void;
- spaces: Space[];
-}) {
- const [loading, setLoading] = useState(false);
-
- const { pending } = useFormStatus();
- return (
- <form
- action={async (e: FormData) => {
- const content = e.get("content")?.toString();
- const space = e.get("space")?.toString();
-
- toast.info("Creating memory...");
-
- if (!content) {
- toast.error("Content is required");
- return;
- }
- cancelfn();
- const cont = await createMemory({
- content: content,
- spaces: space ? [space] : undefined,
- });
-
- if (cont.success) {
- toast.success("Memory created");
- } else {
- toast.error("Memory creation failed");
- }
- }}
- className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3 w-[100vw] md:w-[400px]"
- >
- <div>
- <Label className="text-[#858B92]" htmlFor="space">
- Space
- </Label>
- <Select name="space">
- <SelectTrigger>
- <SelectValue placeholder="Space" />
- </SelectTrigger>
- <SelectContent className="bg-secondary text-white">
- {spaces.map((space) => (
- <SelectItem key={space.id} value={space.id.toString()}>
- {space.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div>
- <Label className="text-[#858B92]" htmlFor="name">
- Resource (URL or content)
- </Label>
- <Textarea
- className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
- id="input"
- name="content"
- placeholder="Start typing a note or paste a URL here. I'll remember it."
- />
- </div>
- <div className="flex justify-end">
- <button
- type="submit"
- className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
- >
- Submit
- </button>
- </div>
- </form>
- );
-}
diff --git a/apps/web/app/(dash)/layout.tsx b/apps/web/app/(dash)/layout.tsx
index 385e46e0..5c7921e4 100644
--- a/apps/web/app/(dash)/layout.tsx
+++ b/apps/web/app/(dash)/layout.tsx
@@ -20,7 +20,7 @@ async function Layout({ children }: { children: React.ReactNode }) {
<div className="relative flex justify-center z-40 pointer-events-none">
<div
- className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-70 blur-[337.4px]"
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
style={{ transform: "rotate(-30deg)" }}
/>
</div>
diff --git a/apps/web/app/(dash)/memories/page.tsx b/apps/web/app/(dash)/memories/page.tsx
index ff746d1d..ca0417b6 100644
--- a/apps/web/app/(dash)/memories/page.tsx
+++ b/apps/web/app/(dash)/memories/page.tsx
@@ -2,10 +2,11 @@
import { getAllUserMemoriesAndSpaces } from "@/app/actions/fetchers";
import { Space } from "@/app/actions/types";
-import { Content } from "@/server/db/schema";
+import { Content, StoredSpace } from "@/server/db/schema";
import { NextIcon, SearchIcon, UrlIcon } from "@repo/ui/icons";
import Image from "next/image";
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
+import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
function Page() {
const [filter, setFilter] = useState("All");
@@ -15,9 +16,34 @@ function Page() {
const [memoriesAndSpaces, setMemoriesAndSpaces] = useState<{
memories: Content[];
- spaces: Space[];
+ spaces: StoredSpace[];
}>({ memories: [], spaces: [] });
+ // Sort Both memories and spaces by their savedAt and createdAt dates respectfully.
+ // The output should be just one single list of items
+ // And it will look something like { item: "memory" | "space", date: Date, data: Content | StoredSpace }
+ const sortedItems = useMemo(() => {
+ // Merge the lists
+ const unifiedItems = [
+ ...memoriesAndSpaces.memories.map((memory) => ({
+ item: "memory",
+ date: new Date(memory.savedAt), // Assuming savedAt is a string date
+ data: memory,
+ })),
+ ...memoriesAndSpaces.spaces.map((space) => ({
+ item: "space",
+ date: new Date(space.createdAt), // Assuming createdAt is a string date
+ data: space,
+ })),
+ ].map((item) => ({
+ ...item,
+ date: Number(item.date), // Convert the date to a number
+ }));
+
+ // Sort the merged list
+ return unifiedItems.sort((a, b) => a.date - b.date);
+ }, [memoriesAndSpaces.memories, memoriesAndSpaces.spaces]);
+
useEffect(() => {
(async () => {
const { success, data } = await getAllUserMemoriesAndSpaces();
@@ -33,33 +59,12 @@ function Page() {
</h2>
<div className="flex flex-col gap-4">
- <div className="w-full relative">
- <input
- type="text"
- className=" w-full py-3 rounded-md text-lg pl-8 bg-[#1F2428] outline-none"
- placeholder="search here..."
- />
- <Image
- className="absolute top-1/2 -translate-y-1/2 left-2"
- src={SearchIcon}
- alt="Search icon"
- />
- </div>
-
- <Filters filter={filter} setFilter={setFilterfn} />
- </div>
- <div>
- <div className="text-[#B3BCC5]">Spaces</div>
- {memoriesAndSpaces.spaces.map((space) => (
- <TabComponent title={space.name} description={space.id.toString()} />
- ))}
- </div>
-
- <div>
- <div className="text-[#B3BCC5]">Pages</div>
- {memoriesAndSpaces.memories.map((memory) => (
- <LinkComponent title={memory.title ?? "No title"} url={memory.url} />
- ))}
+ <ResponsiveMasonry columnsCountBreakPoints={{ 350: 1, 750: 2, 900: 3 }}>
+ <Masonry>
+ <div>Item 1</div>
+ <div>Item 2</div>
+ </Masonry>
+ </ResponsiveMasonry>
</div>
</div>
);
@@ -90,18 +95,22 @@ function TabComponent({
);
}
-function LinkComponent({ title, url }: { title: string; url: string }) {
+function LinkComponent({
+ type,
+ content,
+ title,
+ url,
+}: {
+ type: string;
+ content: string;
+ title: string;
+ url: string;
+}) {
return (
- <div className="flex items-center my-6">
- <div>
- <div className="h-12 w-12 bg-[#1F2428] flex justify-center items-center rounded-md">
- <Image src={UrlIcon} alt="Url icon" />
- </div>
- </div>
- <div className="grow px-4">
- <div className="text-lg text-[#fff]">{title}</div>
- <div>{url}</div>
- </div>
+ <div className="w-full">
+ <div className="text-lg text-[#fff]">{title}</div>
+ <div>{content}</div>
+ <div>{url}</div>
</div>
);
}
@@ -120,7 +129,7 @@ function Filters({
return (
<div
onClick={() => setFilter(i)}
- className={`transition px-6 py-2 rounded-xl ${i === filter ? "bg-[#21303D] text-[#369DFD]" : "text-[#B3BCC5] bg-[#1F2428] hover:bg-[#1f262d] hover:text-[#76a3cc]"}`}
+ className={`transition px-6 py-2 rounded-xl bg-border ${i === filter ? " text-[#369DFD]" : "text-[#B3BCC5] bg-secondary hover:bg-secondary hover:text-[#76a3cc]"}`}
>
{i}
</div>
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index 0622ddc0..d636c8e1 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -37,9 +37,10 @@ import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { createMemory, createSpace } from "../actions/doers";
import { Input } from "@repo/ui/shadcn/input";
import ComboboxWithCreate from "@repo/ui/shadcn/combobox";
+import { StoredSpace } from "@/server/db/schema";
function Menu() {
- const [spaces, setSpaces] = useState<Space[]>([]);
+ const [spaces, setSpaces] = useState<StoredSpace[]>([]);
useEffect(() => {
(async () => {
@@ -269,6 +270,8 @@ function Menu() {
{
name: spaceName,
id: creationTask.data!,
+ createdAt: new Date().toISOString(),
+ user: null,
},
]);
setSelectedSpaces((prev) => [
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
index 664c20ac..558ef3de 100644
--- a/apps/web/app/actions/fetchers.ts
+++ b/apps/web/app/actions/fetchers.ts
@@ -9,6 +9,7 @@ import {
Content,
contentToSpace,
storedContent,
+ StoredSpace,
users,
} from "../../server/db/schema";
import { ServerActionReturnType, Space } from "./types";
@@ -18,7 +19,7 @@ import { ChatHistory as ChatHistoryType } from "../../server/db/schema";
import { z } from "zod";
import { redirect } from "next/navigation";
-export const getSpaces = async (): ServerActionReturnType<Space[]> => {
+export const getSpaces = async (): ServerActionReturnType<StoredSpace[]> => {
const data = await auth();
if (!data || !data.user) {
@@ -30,11 +31,7 @@ export const getSpaces = async (): ServerActionReturnType<Space[]> => {
where: eq(users, data.user.id),
});
- const spacesWithoutUser = spaces.map((space) => {
- return { ...space, user: undefined };
- });
-
- return { success: true, data: spacesWithoutUser };
+ return { success: true, data: spaces };
};
export const getAllMemories = async (
@@ -77,7 +74,7 @@ export const getAllMemories = async (
};
export const getAllUserMemoriesAndSpaces = async (): ServerActionReturnType<{
- spaces: Space[];
+ spaces: StoredSpace[];
memories: Content[];
}> => {
const data = await auth();
diff --git a/apps/web/app/actions/types.ts b/apps/web/app/actions/types.ts
index 5c5afc5c..0b1aaece 100644
--- a/apps/web/app/actions/types.ts
+++ b/apps/web/app/actions/types.ts
@@ -2,6 +2,7 @@ export type Space = {
id: number;
name: string;
numberOfMemories?: number;
+ createdAt?: string;
};
export type ServerActionReturnType<T> = Promise<{
diff --git a/apps/web/migrations/0000_classy_speed_demon.sql b/apps/web/migrations/0000_classy_speed_demon.sql
new file mode 100644
index 00000000..a1885925
--- /dev/null
+++ b/apps/web/migrations/0000_classy_speed_demon.sql
@@ -0,0 +1,113 @@
+CREATE TABLE `account` (
+ `userId` text NOT NULL,
+ `type` text NOT NULL,
+ `provider` text NOT NULL,
+ `providerAccountId` text NOT NULL,
+ `refresh_token` text,
+ `access_token` text,
+ `expires_at` integer,
+ `token_type` text,
+ `scope` text,
+ `id_token` text,
+ `session_state` text,
+ PRIMARY KEY(`provider`, `providerAccountId`),
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `authenticator` (
+ `credentialID` text NOT NULL,
+ `userId` text NOT NULL,
+ `providerAccountId` text NOT NULL,
+ `credentialPublicKey` text NOT NULL,
+ `counter` integer NOT NULL,
+ `credentialDeviceType` text NOT NULL,
+ `credentialBackedUp` integer NOT NULL,
+ `transports` text,
+ PRIMARY KEY(`credentialID`, `userId`),
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `chatHistory` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `threadId` text NOT NULL,
+ `question` text NOT NULL,
+ `answerParts` text,
+ `answerSources` text,
+ `answerJustification` text,
+ FOREIGN KEY (`threadId`) REFERENCES `chatThread`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `chatThread` (
+ `id` text PRIMARY KEY NOT NULL,
+ `firstMessage` text NOT NULL,
+ `userId` text NOT NULL,
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `contentToSpace` (
+ `contentId` integer NOT NULL,
+ `spaceId` integer NOT NULL,
+ PRIMARY KEY(`contentId`, `spaceId`),
+ FOREIGN KEY (`contentId`) REFERENCES `storedContent`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`spaceId`) REFERENCES `space`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `session` (
+ `sessionToken` text PRIMARY KEY NOT NULL,
+ `userId` text NOT NULL,
+ `expires` integer NOT NULL,
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `space` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` text DEFAULT 'none' NOT NULL,
+ `user` text(255),
+ `createdAt` integer NOT NULL,
+ FOREIGN KEY (`user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `storedContent` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `content` text NOT NULL,
+ `title` text(255),
+ `description` text(255),
+ `url` text NOT NULL,
+ `savedAt` integer NOT NULL,
+ `baseUrl` text(255),
+ `ogImage` text(255),
+ `type` text DEFAULT 'page',
+ `image` text(255),
+ `user` text,
+ FOREIGN KEY (`user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `user` (
+ `id` text PRIMARY KEY NOT NULL,
+ `name` text,
+ `email` text NOT NULL,
+ `emailVerified` integer,
+ `image` text,
+ `telegramId` text
+);
+--> statement-breakpoint
+CREATE TABLE `verificationToken` (
+ `identifier` text NOT NULL,
+ `token` text NOT NULL,
+ `expires` integer NOT NULL,
+ PRIMARY KEY(`identifier`, `token`)
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `authenticator_credentialID_unique` ON `authenticator` (`credentialID`);--> statement-breakpoint
+CREATE INDEX `chatHistory_thread_idx` ON `chatHistory` (`threadId`);--> statement-breakpoint
+CREATE INDEX `chatThread_user_idx` ON `chatThread` (`userId`);--> 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 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
+CREATE INDEX `storedContent_user_idx` ON `storedContent` (`user`);--> statement-breakpoint
+CREATE INDEX `users_email_idx` ON `user` (`email`);--> statement-breakpoint
+CREATE INDEX `users_telegram_idx` ON `user` (`telegramId`);--> statement-breakpoint
+CREATE INDEX `users_id_idx` ON `user` (`id`); \ No newline at end of file
diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json
new file mode 100644
index 00000000..291848ad
--- /dev/null
+++ b/apps/web/migrations/meta/0000_snapshot.json
@@ -0,0 +1,685 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "6988522d-8117-484d-b52a-94c0fbd75140",
+ "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": {}
+ },
+ "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
+ }
+ },
+ "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
+ }
+ },
+ "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": {}
+ },
+ "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
+ }
+ },
+ "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
+ }
+ },
+ "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
new file mode 100644
index 00000000..552ae12f
--- /dev/null
+++ b/apps/web/migrations/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "6",
+ "when": 1719671614406,
+ "tag": "0000_classy_speed_demon",
+ "breakpoints": true
+ }
+ ]
+}
diff --git a/apps/web/server/db/schema.ts b/apps/web/server/db/schema.ts
index 69372a35..b5d9bbdb 100644
--- a/apps/web/server/db/schema.ts
+++ b/apps/web/server/db/schema.ts
@@ -151,6 +151,7 @@ export const space = createTable(
user: text("user", { length: 255 }).references(() => users.id, {
onDelete: "cascade",
}),
+ createdAt: int("createdAt", { mode: "timestamp" }).notNull(),
},
(space) => ({
nameIdx: index("spaces_name_idx").on(space.name),