diff --git a/drizzle/schema.ts b/drizzle/schema.ts new file mode 100644 index 0000000..8bcc296 --- /dev/null +++ b/drizzle/schema.ts @@ -0,0 +1,60 @@ +import { pgTable, foreignKey, unique, uuid, text, boolean, date } from "drizzle-orm/pg-core" + import { sql } from "drizzle-orm" + + + +export const groups = pgTable("groups", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + userId: uuid("user_id").references(() => users.id), + email: text("email").notNull(), + name: text("name").notNull(), +}, +(table) => { + return { + groupsEmailUnique: unique("groups_email_unique").on(table.email), + } +}); + +export const sources = pgTable("sources", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + groupId: uuid("group_id").notNull().references(() => groups.id), + name: text("name").notNull(), + isConfirmed: boolean("is_confirmed").default(false).notNull(), +}); + +export const letters = pgTable("letters", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + sender: text("sender").notNull(), + content: text("content").notNull(), + groupId: uuid("group_id").notNull().references(() => groups.id), + dateCreated: date("date_created").defaultNow().notNull(), + senderEmail: text("sender_email").notNull(), + subject: text("subject").notNull(), +}); + +export const sessions = pgTable("sessions", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + userId: uuid("user_id").notNull(), + dateCreated: date("date_created").defaultNow().notNull(), +}); + +export const users = pgTable("users", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + name: text("name").notNull(), + email: text("email").notNull(), + hashedPassword: text("hashed_password").notNull(), + dateCreated: date("date_created").defaultNow().notNull(), +}, +(table) => { + return { + usersEmailUnique: unique("users_email_unique").on(table.email), + } +}); + +export const pods = pgTable("pods", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + userId: uuid("user_id").references(() => users.id), + script: text("script").notNull(), + dateCreated: date("date_created").defaultNow().notNull(), + groupId: uuid("group_id").references(() => groups.id), +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9a2b881..eeff40d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,10 +15,13 @@ "drizzle-orm": "^0.30.1", "formsnap": "^0.5.1", "lucide-svelte": "^0.354.0", + "mode-watcher": "^0.2.2", "pg": "^8.11.3", + "svelte-sonner": "^0.3.19", "sveltekit-superforms": "^2.8.1", "tailwind-merge": "^2.2.1", "tailwind-variants": "^0.2.0", + "vaul-svelte": "^0.3.0", "zod": "^3.22.4" }, "devDependencies": { @@ -3186,6 +3189,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mode-watcher": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.2.2.tgz", + "integrity": "sha512-QjkHQL9pXrr7Vb0P3WbOWAF8mv1Q6jEwUZ5GUyCnI9eEoXH234zuaOGChUF7ZQtjxwtmXDzKFSW/36TvLDg1/A==", + "peerDependencies": { + "svelte": "^4.0.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -4474,6 +4485,14 @@ } } }, + "node_modules/svelte-sonner": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.19.tgz", + "integrity": "sha512-jpPOgLtHwRaB6Vqo2dUQMv15/yUV/BQWTjKpEqQ11uqRSHKjAYUKZyGrHB2cQsGmyjR0JUzBD58btpgNqINQ/Q==", + "peerDependencies": { + "svelte": ">=3 <5" + } + }, "node_modules/sveltekit-superforms": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.8.1.tgz", @@ -4896,6 +4915,63 @@ "node": ">= 0.10" } }, + "node_modules/vaul-svelte": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/vaul-svelte/-/vaul-svelte-0.3.0.tgz", + "integrity": "sha512-+PBfKDWl+xfloe8Tm1G8x3TqbCiUWoyUedU2WC5iE3v6LOYPKo8FyEtzNC5ZqFVVnUKSKNg+4Fi73nuzMkT7JA==", + "dependencies": { + "bits-ui": "^0.16.0" + }, + "peerDependencies": { + "svelte": "^4.0.0" + } + }, + "node_modules/vaul-svelte/node_modules/@melt-ui/svelte": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.68.0.tgz", + "integrity": "sha512-/QvA98hnYEodZtHJ71+ocum/WWp30hVNt3F8uiZKnNYwZDaiQYjlyR9AaGKYcZLCe6R68op1mfCzc0kTzJilyA==", + "dependencies": { + "@floating-ui/core": "^1.3.1", + "@floating-ui/dom": "^1.4.5", + "@internationalized/date": "^3.5.0", + "dequal": "^2.0.3", + "focus-trap": "^7.5.2", + "nanoid": "^5.0.4" + }, + "peerDependencies": { + "svelte": ">=3 <5" + } + }, + "node_modules/vaul-svelte/node_modules/bits-ui": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.16.0.tgz", + "integrity": "sha512-HEkuVDyUG9dTWtKujKpdDsGOe9GRmuYOEF9yGbjVwNazxMQDQa9deUX8vM3ofGBWaJgr1cEu88p38kP5Z5gQ8w==", + "dependencies": { + "@internationalized/date": "^3.5.1", + "@melt-ui/svelte": "0.68.0", + "nanoid": "^5.0.4" + }, + "peerDependencies": { + "svelte": "^4.0.0" + } + }, + "node_modules/vaul-svelte/node_modules/nanoid": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz", + "integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/vite": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", diff --git a/package.json b/package.json index 97b4e98..85d6aef 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,13 @@ "drizzle-orm": "^0.30.1", "formsnap": "^0.5.1", "lucide-svelte": "^0.354.0", + "mode-watcher": "^0.2.2", "pg": "^8.11.3", + "svelte-sonner": "^0.3.19", "sveltekit-superforms": "^2.8.1", "tailwind-merge": "^2.2.1", "tailwind-variants": "^0.2.0", + "vaul-svelte": "^0.3.0", "zod": "^3.22.4" } } diff --git a/src/lib/components/organisms/create-group.svelte b/src/lib/components/organisms/create-group.svelte new file mode 100644 index 0000000..7396846 --- /dev/null +++ b/src/lib/components/organisms/create-group.svelte @@ -0,0 +1,57 @@ + + + + + + + +
+ + Group title + Set a memorable name for your group. + +
+ +
+ + + + + + +
+
+
diff --git a/src/lib/components/ui/drawer/drawer-content.svelte b/src/lib/components/ui/drawer/drawer-content.svelte new file mode 100644 index 0000000..a4e3d9a --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-content.svelte @@ -0,0 +1,24 @@ + + + + + +
+ + + diff --git a/src/lib/components/ui/drawer/drawer-description.svelte b/src/lib/components/ui/drawer/drawer-description.svelte new file mode 100644 index 0000000..1f4c12b --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-description.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/ui/drawer/drawer-footer.svelte b/src/lib/components/ui/drawer/drawer-footer.svelte new file mode 100644 index 0000000..6685813 --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-footer.svelte @@ -0,0 +1,16 @@ + + +
+ +
diff --git a/src/lib/components/ui/drawer/drawer-header.svelte b/src/lib/components/ui/drawer/drawer-header.svelte new file mode 100644 index 0000000..265f291 --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-header.svelte @@ -0,0 +1,19 @@ + + +
+ +
diff --git a/src/lib/components/ui/drawer/drawer-nested.svelte b/src/lib/components/ui/drawer/drawer-nested.svelte new file mode 100644 index 0000000..79b68e3 --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-nested.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/src/lib/components/ui/drawer/drawer-overlay.svelte b/src/lib/components/ui/drawer/drawer-overlay.svelte new file mode 100644 index 0000000..f0b64d6 --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-overlay.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/ui/drawer/drawer-title.svelte b/src/lib/components/ui/drawer/drawer-title.svelte new file mode 100644 index 0000000..fd0a02b --- /dev/null +++ b/src/lib/components/ui/drawer/drawer-title.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/ui/drawer/drawer.svelte b/src/lib/components/ui/drawer/drawer.svelte new file mode 100644 index 0000000..40eae5e --- /dev/null +++ b/src/lib/components/ui/drawer/drawer.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/src/lib/components/ui/drawer/index.ts b/src/lib/components/ui/drawer/index.ts new file mode 100644 index 0000000..76932e3 --- /dev/null +++ b/src/lib/components/ui/drawer/index.ts @@ -0,0 +1,41 @@ +import { Drawer as DrawerPrimitive } from "vaul-svelte"; + +import Root from "./drawer.svelte"; +import Content from "./drawer-content.svelte"; +import Description from "./drawer-description.svelte"; +import Overlay from "./drawer-overlay.svelte"; +import Footer from "./drawer-footer.svelte"; +import Header from "./drawer-header.svelte"; +import Title from "./drawer-title.svelte"; +import NestedRoot from "./drawer-nested.svelte"; + +const Trigger = DrawerPrimitive.Trigger; +const Portal = DrawerPrimitive.Portal; +const Close = DrawerPrimitive.Close; + +export { + Root, + NestedRoot, + Content, + Description, + Overlay, + Footer, + Header, + Title, + Trigger, + Portal, + Close, + + // + Root as Drawer, + NestedRoot as DrawerNestedRoot, + Content as DrawerContent, + Description as DrawerDescription, + Overlay as DrawerOverlay, + Footer as DrawerFooter, + Header as DrawerHeader, + Title as DrawerTitle, + Trigger as DrawerTrigger, + Portal as DrawerPortal, + Close as DrawerClose, +}; diff --git a/src/lib/components/ui/sonner/index.ts b/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..1ad9f4a --- /dev/null +++ b/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/src/lib/components/ui/sonner/sonner.svelte b/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..7d5b2f1 --- /dev/null +++ b/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index b14482f..b1becba 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -1,4 +1,4 @@ -import { date, pgTable, serial, text, uuid, varchar } from 'drizzle-orm/pg-core'; +import { boolean, date, pgTable, serial, text, uuid, varchar } from 'drizzle-orm/pg-core'; import { drizzle } from 'drizzle-orm/node-postgres'; export const usersTable = pgTable('users', { @@ -13,6 +13,7 @@ export const podsTable = pgTable('pods', { id: uuid('id').primaryKey().defaultRandom(), user_id: uuid('user_id').references(() => usersTable.id), script: text('script').notNull(), + groupId: uuid("group_id").references(() => groupsTable.id), date_created: date('date_created').notNull().defaultNow() }); @@ -20,4 +21,28 @@ export const sessionsTable = pgTable('sessions', { id: uuid('id').primaryKey().defaultRandom(), user_id: uuid('user_id').notNull(), date_created: date('date_created').notNull().defaultNow() -}) \ No newline at end of file +}) + +export const groupsTable = pgTable("groups", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + userId: uuid("user_id").references(() => usersTable.id), + email: text("email").notNull().unique(), + name: text("name").notNull(), +}); + +export const sourcesTable = pgTable("sources", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + groupId: uuid("group_id").notNull().references(() => groupsTable.id), + name: text("name").notNull(), + isConfirmed: boolean("is_confirmed").default(false).notNull(), +}); + +export const lettersTable = pgTable("letters", { + id: uuid("id").defaultRandom().primaryKey().notNull(), + sender: text("sender").notNull(), + content: text("content").notNull(), + groupId: uuid("group_id").notNull().references(() => groupsTable.id), + dateCreated: date("date_created").defaultNow().notNull(), + senderEmail: text("sender_email").notNull(), + subject: text("subject").notNull(), +}); diff --git a/src/lib/services/auth.server.ts b/src/lib/services/auth.server.ts index da23026..93de3af 100644 --- a/src/lib/services/auth.server.ts +++ b/src/lib/services/auth.server.ts @@ -12,7 +12,10 @@ export async function createSession(user_id: string) { } export async function validateSession(session_id: string) { - const session = await db.select().from(sessionsTable).where(eq(sessionsTable.id, session_id)).leftJoin(usersTable, eq(usersTable.id, sessionsTable.user_id)); - - return session[0].users; + try { + const session = await db.select().from(sessionsTable).where(eq(sessionsTable.id, session_id)).leftJoin(usersTable, eq(usersTable.id, sessionsTable.user_id)); + return session[0].users; + } catch (e) { + return false; + } } \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c3fffca..86cd539 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,10 @@ +
+ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f2c2eb5..06dc57e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,23 +4,23 @@ import { Button } from '$lib/components/ui/button'; import Player from '$lib/components/organisms/player.svelte'; import { onMount } from 'svelte'; - import { PlayIcon, Loader, Plus, MailIcon } from 'lucide-svelte'; + import { PlayIcon, Loader, Plus, MailIcon, PlusIcon } from 'lucide-svelte'; + import CreateGroup from '$lib/components/organisms/create-group.svelte'; let script = ""; let opened = false; - let pods = []; + let groups = []; + let pods = [] onMount(async () => { let res = await (await fetch("/")).json(); console.log(res); script = res.script; - res = await (await fetch("/api/pods")).json(); - console.log(res); - pods = res.pods; + res = await (await fetch("/api/groups")).json(); + groups = res.groups; }); - {#if opened} @@ -41,16 +41,16 @@ {/each} - +
- {#each pods as pod} + + {#each groups as group} {/each} diff --git a/src/routes/api/groups/+server.js b/src/routes/api/groups/+server.js new file mode 100644 index 0000000..8f00bfb --- /dev/null +++ b/src/routes/api/groups/+server.js @@ -0,0 +1,46 @@ +import { db, groupsTable } from "$lib/db" +import { generateId } from "$lib/utils/auth.utils"; +import { json } from "@sveltejs/kit"; +import { eq } from "drizzle-orm"; + +/** + * @type {import("@sveltejs/kit").RequestHandler} + */ +export async function GET(event) { + console.log(event.locals.user.id) + const groups = await db.select().from(groupsTable).where(eq(groupsTable.userId, event.locals.user.id)); + + return new Response(JSON.stringify({ + success: true, + groups + })); +} + +/** + * @type {import("@sveltejs/kit").RequestHandler} + */ +export async function POST(event) { + const body = await event.request.json(); + if (!body.name) return json({ + success: false + }); + + const values = { + name: body.name, + email: `${generateId(7)}@smtp.omersabic.com`, + userId: event.locals.user.id + }; + + const groups = await db.insert(groupsTable).values(values).returning(); + + console.log(groups); + + if (groups.length === 0) return json({ + succcess: false + }); + + return json({ + success: true, + group: groups[0] + }); +} \ No newline at end of file diff --git a/src/routes/auth/+page.server.js b/src/routes/auth/+page.server.js index 0ffa34c..aa5078e 100644 --- a/src/routes/auth/+page.server.js +++ b/src/routes/auth/+page.server.js @@ -7,6 +7,7 @@ import bcrypt from "bcrypt"; import * as authService from "$lib/services/auth.server"; import { eq } from "drizzle-orm"; import { hashPassword, validatePassword } from "$lib/utils/auth.utils"; + /** * @type {import("./$types").PageServerLoad} */