groups api route
This commit is contained in:
parent
3b1e627356
commit
d62cc6f6a4
60
drizzle/schema.ts
Normal file
60
drizzle/schema.ts
Normal file
@ -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),
|
||||||
|
});
|
76
package-lock.json
generated
76
package-lock.json
generated
@ -15,10 +15,13 @@
|
|||||||
"drizzle-orm": "^0.30.1",
|
"drizzle-orm": "^0.30.1",
|
||||||
"formsnap": "^0.5.1",
|
"formsnap": "^0.5.1",
|
||||||
"lucide-svelte": "^0.354.0",
|
"lucide-svelte": "^0.354.0",
|
||||||
|
"mode-watcher": "^0.2.2",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"svelte-sonner": "^0.3.19",
|
||||||
"sveltekit-superforms": "^2.8.1",
|
"sveltekit-superforms": "^2.8.1",
|
||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwind-variants": "^0.2.0",
|
"tailwind-variants": "^0.2.0",
|
||||||
|
"vaul-svelte": "^0.3.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -3186,6 +3189,14 @@
|
|||||||
"mkdirp": "bin/cmd.js"
|
"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": {
|
"node_modules/mri": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
"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": {
|
"node_modules/sveltekit-superforms": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.8.1.tgz",
|
||||||
@ -4896,6 +4915,63 @@
|
|||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/vite": {
|
||||||
"version": "5.1.5",
|
"version": "5.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz",
|
||||||
|
@ -39,10 +39,13 @@
|
|||||||
"drizzle-orm": "^0.30.1",
|
"drizzle-orm": "^0.30.1",
|
||||||
"formsnap": "^0.5.1",
|
"formsnap": "^0.5.1",
|
||||||
"lucide-svelte": "^0.354.0",
|
"lucide-svelte": "^0.354.0",
|
||||||
|
"mode-watcher": "^0.2.2",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"svelte-sonner": "^0.3.19",
|
||||||
"sveltekit-superforms": "^2.8.1",
|
"sveltekit-superforms": "^2.8.1",
|
||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwind-variants": "^0.2.0",
|
"tailwind-variants": "^0.2.0",
|
||||||
|
"vaul-svelte": "^0.3.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
src/lib/components/organisms/create-group.svelte
Normal file
57
src/lib/components/organisms/create-group.svelte
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<script>
|
||||||
|
import { PlusIcon } from 'lucide-svelte';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import * as Drawer from '../ui/drawer';
|
||||||
|
import { Input } from '../ui/input';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
|
let group_name = '';
|
||||||
|
|
||||||
|
async function createGroup() {
|
||||||
|
if (!group_name) return;
|
||||||
|
|
||||||
|
const groups = await (
|
||||||
|
await fetch('/api/groups', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: group_name
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).json();
|
||||||
|
|
||||||
|
if (!groups.success) return toast.error('Problem with creating group.');
|
||||||
|
return toast.success('Group created.');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Drawer.Root>
|
||||||
|
<Drawer.Trigger asChild let:builder>
|
||||||
|
<Button builders={[builder]} class="w-full rounded" variant="outline">
|
||||||
|
<div class="flex flex-row justify-center items-center">
|
||||||
|
<div class="items-center justify-center rounded-lg p-2 pl-0">
|
||||||
|
<PlusIcon class="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-md">Create Group</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Drawer.Trigger>
|
||||||
|
<Drawer.Content>
|
||||||
|
<div class="mx-auto w-full max-w-sm">
|
||||||
|
<Drawer.Header>
|
||||||
|
<Drawer.Title>Group title</Drawer.Title>
|
||||||
|
<Drawer.Description>Set a memorable name for your group.</Drawer.Description>
|
||||||
|
</Drawer.Header>
|
||||||
|
<div>
|
||||||
|
<Input type="text" bind:value={group_name} class="bg-primary" />
|
||||||
|
</div>
|
||||||
|
<Drawer.Footer>
|
||||||
|
<Button on:click={createGroup}>Submit</Button>
|
||||||
|
<Drawer.Close asChild let:builder>
|
||||||
|
<Button builders={[builder]} variant="outline">Cancel</Button>
|
||||||
|
</Drawer.Close>
|
||||||
|
</Drawer.Footer>
|
||||||
|
</div>
|
||||||
|
</Drawer.Content>
|
||||||
|
</Drawer.Root>
|
24
src/lib/components/ui/drawer/drawer-content.svelte
Normal file
24
src/lib/components/ui/drawer/drawer-content.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import DrawerOverlay from "./drawer-overlay.svelte";
|
||||||
|
import { cn } from "$lib/utils.js.js";
|
||||||
|
|
||||||
|
type $$Props = DrawerPrimitive.ContentProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Portal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
class={cn(
|
||||||
|
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border border-black bg-secondary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
<slot />
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPrimitive.Portal>
|
18
src/lib/components/ui/drawer/drawer-description.svelte
Normal file
18
src/lib/components/ui/drawer/drawer-description.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import { cn } from "$lib/utils.js.js";
|
||||||
|
|
||||||
|
type $$Props = DrawerPrimitive.DescriptionProps;
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
bind:el
|
||||||
|
class={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DrawerPrimitive.Description>
|
16
src/lib/components/ui/drawer/drawer-footer.svelte
Normal file
16
src/lib/components/ui/drawer/drawer-footer.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
el?: HTMLDivElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={el} class={cn("mt-auto flex flex-col gap-2 p-4", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
19
src/lib/components/ui/drawer/drawer-header.svelte
Normal file
19
src/lib/components/ui/drawer/drawer-header.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
el?: HTMLDivElement;
|
||||||
|
};
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={el}
|
||||||
|
class={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
12
src/lib/components/ui/drawer/drawer-nested.svelte
Normal file
12
src/lib/components/ui/drawer/drawer-nested.svelte
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
type $$Props = DrawerPrimitive.Props;
|
||||||
|
export let shouldScaleBackground: $$Props["shouldScaleBackground"] = true;
|
||||||
|
export let open: $$Props["open"] = false;
|
||||||
|
export let activeSnapPoint: $$Props["activeSnapPoint"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.NestedRoot {shouldScaleBackground} bind:open bind:activeSnapPoint {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</DrawerPrimitive.NestedRoot>
|
18
src/lib/components/ui/drawer/drawer-overlay.svelte
Normal file
18
src/lib/components/ui/drawer/drawer-overlay.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import { cn } from "$lib/utils.js.js";
|
||||||
|
|
||||||
|
type $$Props = DrawerPrimitive.OverlayProps;
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
bind:el
|
||||||
|
class={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DrawerPrimitive.Overlay>
|
18
src/lib/components/ui/drawer/drawer-title.svelte
Normal file
18
src/lib/components/ui/drawer/drawer-title.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import { cn } from "$lib/utils.js.js";
|
||||||
|
|
||||||
|
type $$Props = DrawerPrimitive.TitleProps;
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
bind:el
|
||||||
|
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DrawerPrimitive.Title>
|
12
src/lib/components/ui/drawer/drawer.svelte
Normal file
12
src/lib/components/ui/drawer/drawer.svelte
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
type $$Props = DrawerPrimitive.Props;
|
||||||
|
export let shouldScaleBackground: $$Props["shouldScaleBackground"] = true;
|
||||||
|
export let open: $$Props["open"] = false;
|
||||||
|
export let activeSnapPoint: $$Props["activeSnapPoint"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Root {shouldScaleBackground} bind:open bind:activeSnapPoint {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</DrawerPrimitive.Root>
|
41
src/lib/components/ui/drawer/index.ts
Normal file
41
src/lib/components/ui/drawer/index.ts
Normal file
@ -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,
|
||||||
|
};
|
1
src/lib/components/ui/sonner/index.ts
Normal file
1
src/lib/components/ui/sonner/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Toaster } from "./sonner.svelte";
|
20
src/lib/components/ui/sonner/sonner.svelte
Normal file
20
src/lib/components/ui/sonner/sonner.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
|
||||||
|
import { mode } from "mode-watcher";
|
||||||
|
|
||||||
|
type $$Props = SonnerProps;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sonner
|
||||||
|
theme={$mode}
|
||||||
|
class="toaster group"
|
||||||
|
toastOptions={{
|
||||||
|
classes: {
|
||||||
|
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||||
|
description: "group-[.toast]:text-muted-foreground",
|
||||||
|
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||||
|
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
@ -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';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
|
||||||
export const usersTable = pgTable('users', {
|
export const usersTable = pgTable('users', {
|
||||||
@ -13,6 +13,7 @@ export const podsTable = pgTable('pods', {
|
|||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
user_id: uuid('user_id').references(() => usersTable.id),
|
user_id: uuid('user_id').references(() => usersTable.id),
|
||||||
script: text('script').notNull(),
|
script: text('script').notNull(),
|
||||||
|
groupId: uuid("group_id").references(() => groupsTable.id),
|
||||||
date_created: date('date_created').notNull().defaultNow()
|
date_created: date('date_created').notNull().defaultNow()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -21,3 +22,27 @@ export const sessionsTable = pgTable('sessions', {
|
|||||||
user_id: uuid('user_id').notNull(),
|
user_id: uuid('user_id').notNull(),
|
||||||
date_created: date('date_created').notNull().defaultNow()
|
date_created: date('date_created').notNull().defaultNow()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
@ -12,7 +12,10 @@ export async function createSession(user_id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function validateSession(session_id: string) {
|
export async function validateSession(session_id: string) {
|
||||||
|
try {
|
||||||
const session = await db.select().from(sessionsTable).where(eq(sessionsTable.id, session_id)).leftJoin(usersTable, eq(usersTable.id, sessionsTable.user_id));
|
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;
|
return session[0].users;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.pcss';
|
import '../app.pcss';
|
||||||
|
import { Toaster } from '$lib/components/ui/sonner';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Toaster />
|
||||||
<div class="max-w-xl mx-auto p-8 h-screen relative">
|
<div class="max-w-xl mx-auto p-8 h-screen relative">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,23 +4,23 @@
|
|||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import Player from '$lib/components/organisms/player.svelte';
|
import Player from '$lib/components/organisms/player.svelte';
|
||||||
import { onMount } from '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 script = "";
|
||||||
|
|
||||||
let opened = false;
|
let opened = false;
|
||||||
let pods = [];
|
let groups = [];
|
||||||
|
let pods = []
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let res = await (await fetch("/")).json();
|
let res = await (await fetch("/")).json();
|
||||||
console.log(res);
|
console.log(res);
|
||||||
script = res.script;
|
script = res.script;
|
||||||
|
|
||||||
res = await (await fetch("/api/pods")).json();
|
res = await (await fetch("/api/groups")).json();
|
||||||
console.log(res);
|
groups = res.groups;
|
||||||
pods = res.pods;
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if opened}
|
{#if opened}
|
||||||
@ -41,16 +41,16 @@
|
|||||||
</Button>
|
</Button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<Label>Your Newsletters</Label>
|
<Label>Your Groups</Label>
|
||||||
<div class="overflow-x-scroll scrollbars-hidden">
|
<div class="overflow-x-scroll scrollbars-hidden">
|
||||||
{#each pods as pod}
|
<CreateGroup />
|
||||||
|
{#each groups as group}
|
||||||
<Button class="mt-4 flex w-full flex-row justify-start rounded text-left" variant="secondary">
|
<Button class="mt-4 flex w-full flex-row justify-start rounded text-left" variant="secondary">
|
||||||
<div class="items-center justify-center rounded-lg p-2 pl-0">
|
<div class="items-center justify-center rounded-lg p-2 pl-0">
|
||||||
<MailIcon class="h-4 w-4" />
|
<MailIcon class="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-md">{pod.title}</h3>
|
<h3 class="text-md">{group.name}</h3>
|
||||||
<p class="text-sm text-muted-foreground">{new Date(pod.date).toLocaleDateString('de')}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
{/each}
|
{/each}
|
||||||
|
46
src/routes/api/groups/+server.js
Normal file
46
src/routes/api/groups/+server.js
Normal file
@ -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]
|
||||||
|
});
|
||||||
|
}
|
@ -7,6 +7,7 @@ import bcrypt from "bcrypt";
|
|||||||
import * as authService from "$lib/services/auth.server";
|
import * as authService from "$lib/services/auth.server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { hashPassword, validatePassword } from "$lib/utils/auth.utils";
|
import { hashPassword, validatePassword } from "$lib/utils/auth.utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("./$types").PageServerLoad}
|
* @type {import("./$types").PageServerLoad}
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user