privacy mode (#22)
This commit is contained in:
parent
954b7173f7
commit
d83cd134ff
6 changed files with 182 additions and 9 deletions
|
|
@ -2,12 +2,13 @@ import { betterAuth } from 'better-auth';
|
|||
import { convexAdapter } from '@better-auth-kit/convex';
|
||||
import { ConvexHttpClient } from 'convex/browser';
|
||||
import 'dotenv/config';
|
||||
import { api } from './backend/convex/_generated/api';
|
||||
|
||||
const convexClient = new ConvexHttpClient(process.env.PUBLIC_CONVEX_URL!);
|
||||
const client = new ConvexHttpClient(process.env.PUBLIC_CONVEX_URL!);
|
||||
|
||||
export const auth = betterAuth({
|
||||
secret: process.env.BETTER_AUTH_SECRET!,
|
||||
database: convexAdapter(convexClient),
|
||||
database: convexAdapter(client),
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID!,
|
||||
|
|
@ -17,7 +18,12 @@ export const auth = betterAuth({
|
|||
databaseHooks: {
|
||||
user: {
|
||||
create: {
|
||||
after: async (_user) => {},
|
||||
after: async (user) => {
|
||||
// create user settings
|
||||
await client.mutation(api.user_settings.create, {
|
||||
user_id: user.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ export type MessageRole = Infer<typeof messageRoleValidator>;
|
|||
export const ruleAttachValidator = v.union(v.literal('always'), v.literal('manual'));
|
||||
|
||||
export default defineSchema({
|
||||
user_settings: defineTable({
|
||||
user_id: v.string(),
|
||||
privacy_mode: v.boolean(),
|
||||
}).index('by_user', ['user_id']),
|
||||
user_keys: defineTable({
|
||||
user_id: v.string(),
|
||||
provider: providerValidator,
|
||||
|
|
|
|||
74
src/lib/backend/convex/user_settings.ts
Normal file
74
src/lib/backend/convex/user_settings.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { internal } from './_generated/api';
|
||||
import { query } from './_generated/server';
|
||||
import { SessionObj } from './betterAuth';
|
||||
import { mutation } from './functions';
|
||||
import { v } from 'convex/values';
|
||||
|
||||
export const get = query({
|
||||
args: {
|
||||
session_token: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const session = await ctx.runQuery(internal.betterAuth.getSession, {
|
||||
sessionToken: args.session_token,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
throw new Error('Invalid session token');
|
||||
}
|
||||
|
||||
const s = session as SessionObj;
|
||||
|
||||
return await ctx.db
|
||||
.query('user_settings')
|
||||
.withIndex('by_user', (q) => q.eq('user_id', s.userId))
|
||||
.first();
|
||||
},
|
||||
});
|
||||
|
||||
export const set = mutation({
|
||||
args: {
|
||||
privacy_mode: v.boolean(),
|
||||
session_token: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const session = await ctx.runQuery(internal.betterAuth.getSession, {
|
||||
sessionToken: args.session_token,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
throw new Error('Invalid session token');
|
||||
}
|
||||
|
||||
const s = session as SessionObj;
|
||||
|
||||
const existing = await ctx.db
|
||||
.query('user_settings')
|
||||
.withIndex('by_user', (q) => q.eq('user_id', s.userId))
|
||||
.first();
|
||||
|
||||
if (!existing) {
|
||||
await ctx.db.insert('user_settings', {
|
||||
user_id: s.userId,
|
||||
privacy_mode: args.privacy_mode,
|
||||
});
|
||||
} else {
|
||||
await ctx.db.patch(existing._id, {
|
||||
privacy_mode: args.privacy_mode,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** Never call this from the client */
|
||||
export const create = mutation({
|
||||
args: {
|
||||
user_id: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.insert('user_settings', {
|
||||
user_id: args.user_id,
|
||||
privacy_mode: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -49,6 +49,10 @@
|
|||
await goto(`/chat`);
|
||||
}
|
||||
|
||||
const settings = useCachedQuery(api.user_settings.get, {
|
||||
session_token: session.current?.session.token ?? '',
|
||||
});
|
||||
|
||||
const conversationsQuery = useCachedQuery(api.conversations.get, {
|
||||
session_token: session.current?.session.token ?? '',
|
||||
});
|
||||
|
|
@ -232,7 +236,13 @@
|
|||
<Button href="/account" variant="ghost" class="h-auto w-full justify-start">
|
||||
<Avatar src={page.data.session?.user.image ?? undefined}>
|
||||
{#snippet children(avatar)}
|
||||
<img {...avatar.image} alt="Your avatar" class="size-10 rounded-full" />
|
||||
<img
|
||||
{...avatar.image}
|
||||
alt="Your avatar"
|
||||
class={cn('size-10 rounded-full', {
|
||||
'blur-[6px]': settings.data?.privacy_mode,
|
||||
})}
|
||||
/>
|
||||
<span {...avatar.fallback} class="size-10 rounded-full">
|
||||
{page.data.session?.user.name
|
||||
.split(' ')
|
||||
|
|
@ -242,8 +252,16 @@
|
|||
{/snippet}
|
||||
</Avatar>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm">{page.data.session?.user.name}</span>
|
||||
<span class="text-muted-foreground text-xs">{page.data.session?.user.email}</span>
|
||||
<span class={cn('text-sm', { 'blur-[6px]': settings.data?.privacy_mode })}>
|
||||
{page.data.session?.user.name}
|
||||
</span>
|
||||
<span
|
||||
class={cn('text-muted-foreground text-xs', {
|
||||
'blur-[6px]': settings.data?.privacy_mode,
|
||||
})}
|
||||
>
|
||||
{page.data.session?.user.email}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,17 @@
|
|||
import { Avatar } from 'melt/components';
|
||||
import { Kbd } from '$lib/components/ui/kbd/index.js';
|
||||
import { cmdOrCtrl } from '$lib/hooks/is-mac.svelte.js';
|
||||
import { useCachedQuery } from '$lib/cache/cached-query.svelte.js';
|
||||
import { session } from '$lib/state/session.svelte.js';
|
||||
import { api } from '$lib/backend/convex/_generated/api.js';
|
||||
import { cn } from '$lib/utils/utils.js';
|
||||
|
||||
let { data, children } = $props();
|
||||
|
||||
const settings = useCachedQuery(api.user_settings.get, {
|
||||
session_token: session.current?.session.token ?? '',
|
||||
});
|
||||
|
||||
const navigation: { title: string; href: string }[] = [
|
||||
{
|
||||
title: 'Account',
|
||||
|
|
@ -69,7 +77,13 @@
|
|||
<div class="flex flex-col place-items-center gap-2">
|
||||
<Avatar src={data.session.user.image ?? undefined}>
|
||||
{#snippet children(avatar)}
|
||||
<img {...avatar.image} alt="Your avatar" class="size-40 rounded-full" />
|
||||
<img
|
||||
{...avatar.image}
|
||||
alt="Your avatar"
|
||||
class={cn('size-40 rounded-full', {
|
||||
'blur-[20px]': settings.data?.privacy_mode,
|
||||
})}
|
||||
/>
|
||||
<span {...avatar.fallback}>
|
||||
{data.session.user.name
|
||||
.split(' ')
|
||||
|
|
@ -79,8 +93,20 @@
|
|||
{/snippet}
|
||||
</Avatar>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-center text-2xl font-bold">{data.session.user.name}</p>
|
||||
<span class="text-muted-foreground text-center text-sm">{data.session.user.email}</span>
|
||||
<p
|
||||
class={cn('text-center text-2xl font-bold', {
|
||||
'blur-[6px]': settings.data?.privacy_mode,
|
||||
})}
|
||||
>
|
||||
{data.session.user.name}
|
||||
</p>
|
||||
<span
|
||||
class={cn('text-muted-foreground text-center text-sm', {
|
||||
'blur-[6px]': settings.data?.privacy_mode,
|
||||
})}
|
||||
>
|
||||
{data.session.user.email}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-4 flex w-full flex-col gap-2">
|
||||
<span class="text-sm font-medium">Keyboard Shortcuts</span>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<script lang="ts">
|
||||
import { api } from '$lib/backend/convex/_generated/api';
|
||||
import { useCachedQuery } from '$lib/cache/cached-query.svelte';
|
||||
import { session } from '$lib/state/session.svelte';
|
||||
import { ResultAsync } from 'neverthrow';
|
||||
import { useConvexClient } from 'convex-svelte';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
const settings = useCachedQuery(api.user_settings.get, {
|
||||
session_token: session.current?.session.token ?? '',
|
||||
});
|
||||
|
||||
let privacyMode = $derived(settings.data?.privacy_mode ?? false);
|
||||
|
||||
async function toggleEnabled(v: boolean) {
|
||||
privacyMode = v; // Optimistic!
|
||||
if (!session.current?.user.id) return;
|
||||
|
||||
const res = await ResultAsync.fromPromise(
|
||||
client.mutation(api.user_settings.set, {
|
||||
privacy_mode: v,
|
||||
session_token: session.current?.session.token,
|
||||
}),
|
||||
(e) => e
|
||||
);
|
||||
|
||||
if (res.isErr()) privacyMode = !v; // Should have been a realist :(
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Account | Thom.chat</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="text-2xl font-bold">Account Settings</h1>
|
||||
<h2 class="text-muted-foreground mt-2 text-sm">Configure the settings for your account.</h2>
|
||||
|
||||
<div class="mt-4 flex flex-col gap-2">
|
||||
<div class="flex place-items-center justify-between">
|
||||
<span>Hide Personal Information</span>
|
||||
<Switch bind:value={() => privacyMode, toggleEnabled} />
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Add table
Reference in a new issue