Skip to content
This repository has been archived by the owner on Nov 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #19 from recoskyler/recoskyler-dev
Browse files Browse the repository at this point in the history
Fixes and simple stats panel
  • Loading branch information
recoskyler authored Jan 11, 2024
2 parents 65a23f2 + 7ea52f5 commit ee233aa
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 40 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chatter",
"version": "1.0.0",
"version": "1.1.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
Expand Down
3 changes: 3 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ PUBLIC_MAX_CHATS=

# Optional, defaults to 25. Includes both deleted (in trash) and available accounts
PUBLIC_MAX_ACCOUNTS=

# Required, the default email address to be used with Wizard Tower (Admin Panel)
WIZARD_TOWER_DEFAULT_EMAIL=example@example.com
73 changes: 73 additions & 0 deletions src/lib/components/AnalyticsAccordion.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script lang="ts">
import {
AccordionItem,
SlideToggle,
} from "@skeletonlabs/skeleton";
import type { PageData } from "../../routes/app/profile/$types";
import Fa from "svelte-fa";
import {
faChartSimple,
} from "@fortawesome/free-solid-svg-icons";
import { getCookie, setCookie } from "$lib/functions/helper";
import { onMount } from "svelte";
import { browser } from "$app/environment";
import { DO_NOT_TRACK_COOKIE_NAME } from "$lib/constants";
let analyticsEnabled = false;
const toggleAnalytics = () => {
if (!browser) {
console.error("Not a browser. Cannot save settings");
return;
}
setCookie(
DO_NOT_TRACK_COOKIE_NAME,
analyticsEnabled ? "false" : "true",
365,
);
console.info(`${analyticsEnabled ? "Enabled" : "Disabled"} analytics`);
};
onMount(() => {
const cookieVal = getCookie(DO_NOT_TRACK_COOKIE_NAME);
analyticsEnabled = cookieVal === "false" || cookieVal === "" ? true : false;
});
</script>

<AccordionItem>
<svelte:fragment slot="lead">
<Fa fw icon={faChartSimple} />
</svelte:fragment>

<svelte:fragment slot="summary">Analytics</svelte:fragment>

<svelte:fragment slot="content">
<p>
<span class="text-orange-600 dark:text-orange-400"
><strong>WARNING: </strong></span
>
<!-- eslint-disable-next-line max-len -->
This setting is stored in a cookie. When you sign out, clear the browser cookies,
or block cookies it will be reset to its default state (<strong
>Disabled</strong
>)
</p>

<SlideToggle
name="enabled"
bind:checked={analyticsEnabled}
on:change={toggleAnalytics}
bgDark="bg-surface-400"
class="my-3"
>
Enable analytics?
</SlideToggle>

<p>
<strong>Please refresh the page after changing this option</strong>
</p>
</svelte:fragment>
</AccordionItem>
9 changes: 9 additions & 0 deletions src/lib/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ export const account = pgTable("account", {
deleted: boolean("deleted").notNull().default(false),
});

export const userRoleEnum = pgEnum(
"user_role",
[
"user",
"admin",
],
);

export const userConfig = pgTable("user_config", {
id: uuid("id").primaryKey().defaultRandom(),
userId: text("user_id")
Expand All @@ -72,6 +80,7 @@ export const userConfig = pgTable("user_config", {
.references(() => user.id),
defaultAccountId: uuid("default_account_id")
.references(() => account.id),
userRole: userRoleEnum("user_role").notNull().default("user"),
});

export const chat = pgTable("chat", {
Expand Down
1 change: 1 addition & 0 deletions src/lib/stores/currentPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const CHATTER_PAGE = {
CHATS: "CHATS",
ACCOUNTS: "ACCOUNTS",
PROFILE: "PROFILE",
TOWER: "TOWER",
} as const;

type ObjectValues<T> = T[keyof T];
Expand Down
5 changes: 3 additions & 2 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@

<svelte:head>
{#if doNotTrack !== ""}
<script
<!-- <script
async
defer
src="https://umami.recoskyler.com/script.js"
data-website-id="607f67a0-703e-42b6-8397-eb932fb71ba6"
data-do-not-track={doNotTrack}
data-cache="true"
></script>
></script> -->
<!-- TODO: Enable analytics once there is a good open source option -->
{/if}
</svelte:head>

Expand Down
4 changes: 3 additions & 1 deletion src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
</a>

<a
href="/app"
href="https://github.com/recoskyler/chatter"
class="btn variant-ghost-tertiary"
data-umami-event="View source code button"
target="_blank"
rel="noopener noreferrer"
>
View source code
</a>
Expand Down
5 changes: 3 additions & 2 deletions src/routes/app/profile/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { error, redirect } from "@sveltejs/kit";
import {
error, redirect, type Actions, fail,
} from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { type Actions, fail } from "@sveltejs/kit";
import { auth } from "$lib/server/lucia";
import {
DO_NOT_TRACK_COOKIE_NAME,
Expand Down
37 changes: 3 additions & 34 deletions src/routes/app/profile/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import AnalyticsAccordion from '../../../lib/components/AnalyticsAccordion.svelte';
import {
Accordion,
AccordionItem,
Expand Down Expand Up @@ -390,40 +392,7 @@
</svelte:fragment>
</AccordionItem>

<AccordionItem>
<svelte:fragment slot="lead">
<Fa fw icon={faChartSimple} />
</svelte:fragment>

<svelte:fragment slot="summary">Analytics</svelte:fragment>

<svelte:fragment slot="content">
<p>
<span class="text-orange-600 dark:text-orange-400"
><strong>WARNING: </strong></span
>
<!-- eslint-disable-next-line max-len -->
This setting is stored in a cookie. When you sign out, clear the browser
cookies, or block cookies it will be reset to its default state (<strong
>Disabled</strong
>)
</p>

<SlideToggle
name="enabled"
bind:checked={analyticsEnabled}
on:change={toggleAnalytics}
bgDark="bg-surface-400"
class="my-3"
>
Enable analytics?
</SlideToggle>

<p>
<strong>Please refresh the page after changing this option</strong>
</p>
</svelte:fragment>
</AccordionItem>
<AnalyticsAccordion />

<AccordionItem>
<svelte:fragment slot="lead">
Expand Down
2 changes: 2 additions & 0 deletions src/routes/app/setup/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "$lib/db/types";
import { seed } from "$lib/db/seed";
import { auth } from "$lib/server/lucia";
import { WIZARD_TOWER_DEFAULT_EMAIL } from "$env/static/private";

export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.auth.validate();
Expand Down Expand Up @@ -102,6 +103,7 @@ export const actions: Actions = {
const config: NewUserConfig = {
userId: session.user.userId,
defaultAccountId: dbAccount.id,
userRole: session.user.email === WIZARD_TOWER_DEFAULT_EMAIL ? "admin" : "user",
};

await db.insert(userConfig).values(config);
Expand Down
25 changes: 25 additions & 0 deletions src/routes/login/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { setError, superValidate } from "sveltekit-superforms/server";
import { z } from "zod";
import { MIN_PASSWORD_LENGTH } from "$lib/constants";
import { signInLimiter } from "$lib/server/limiter";
import { WIZARD_TOWER_DEFAULT_EMAIL } from "$env/static/private";
import { db } from "$lib/server/drizzle";
import { user, userConfig } from "../../lib/db/schema";
import { eq } from "drizzle-orm";

const loginSchema = z.object({
email: z.string().email(),
Expand Down Expand Up @@ -49,6 +53,27 @@ export const actions: Actions = {
const session = await auth.createSession({ userId: key.userId, attributes: {} });

locals.auth.setSession(session);

const userCfg = await db.query.userConfig.findFirst({
where: eq(user.id, key.userId),
columns: { userRole: true },
});

if (!userCfg) {
console.warn("User config not found");

return;
}

if (form.data.email === WIZARD_TOWER_DEFAULT_EMAIL && userCfg.userRole !== "admin") {
await db.update(userConfig)
.set({ userRole: "admin" })
.where(eq(user.id, key.userId));
}

if (form.data.email === WIZARD_TOWER_DEFAULT_EMAIL || userCfg.userRole === "admin") {
throw redirect(302, "/tower");
}
} catch {
return setError(form, "", "Invalid email or password");
}
Expand Down
30 changes: 30 additions & 0 deletions src/routes/tower/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import { db } from "$lib/server/drizzle";
import { error, redirect } from "@sveltejs/kit";
import type { LayoutServerLoad } from "./$types";
import { EMAIL_VERIFICATION } from "$lib/constants";
import { eq } from "drizzle-orm";
import { user } from "$lib/db/schema";

export const load: LayoutServerLoad = async ({ locals }) => {
const session = await locals.auth.validate();

if (!session || (EMAIL_VERIFICATION && !session.user.verified)) {
throw redirect(302, "/login");
}

const dbUser = await db.query.user.findFirst({
with: {
accounts: true,
chats: true,
config: { with: { defaultAccount: true } },
},
where: eq(user.id, session.user.userId),
});

if (!dbUser) throw error(404, "User not found");

if (!dbUser.config) throw redirect(302, "/app/setup");

return { user: dbUser };
};
52 changes: 52 additions & 0 deletions src/routes/tower/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { error, redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { db } from "$lib/server/drizzle";
import {
account, chat, user,
} from "$lib/db/schema";
import { eq, sql } from "drizzle-orm";
import { EMAIL_VERIFICATION } from "$lib/constants";

export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.auth.validate();

if (!session || (EMAIL_VERIFICATION && !session.user.verified)) {
throw redirect(302, "/login");
}

const dbUser = await db.query.user.findFirst({
with: {
chats: true,
accounts: true,
config: { with: { defaultAccount: true } },
},
where: eq(user.id, session.user.userId),
});

if (!dbUser) throw error(404, "User not found");

if (dbUser.config === null) throw redirect(302, "/app/setup");

if (dbUser.config.userRole !== "admin") {
throw redirect(302, "/app");
}

const userCount = await db.select({ count: sql<number>`cast(count(${user.id}) as int)` })
.from(user).execute();

const chatsCount = await db.select({ count: sql<number>`cast(count(${chat.id}) as int)` })
.from(chat).execute();

const accountsCount = await db.select({ count: sql<number>`cast(count(${account.id}) as int)` })
.from(account).execute();

return {
user: dbUser,
stats: {
userCount: userCount[0].count,
chatsCount: chatsCount[0].count,
accountsCount: accountsCount[0].count,
},
};
};

Loading

0 comments on commit ee233aa

Please sign in to comment.