Fix sessions, start profile UI and add middleware

This commit is contained in:
Peaceultime 2024-11-06 17:38:15 +01:00
parent b3fae0b5db
commit a392841012
14 changed files with 74 additions and 26 deletions

View File

@ -3,9 +3,9 @@
<AvatarImage class="h-full w-full object-cover" :src="src" asChild @loading-status-change="(status) => loading = status === 'loading'"> <AvatarImage class="h-full w-full object-cover" :src="src" asChild @loading-status-change="(status) => loading = status === 'loading'">
<img :src="src" /> <img :src="src" />
</AvatarImage> </AvatarImage>
<AvatarFallback :delay-ms="0" class="text-light-100 dark:text-dark-100 leading-1 flex h-full w-full items-center justify-center bg-light-25 dark:bg-dark-25 font-medium"> <AvatarFallback :delay-ms="0" class="text-light-100 dark:text-dark-100 leading-1 flex h-full w-full p-4 items-center justify-center bg-light-25 dark:bg-dark-25 font-medium">
<Loading v-if="loading" /> <Loading v-if="loading" />
<Icon v-else-if="!!icon" :icon="icon" class="w-8 h-8" /> <Icon v-else-if="!!icon" :icon="icon" class="w-full h-full" />
<span v-else-if="!!text">{{ text }}</span> <span v-else-if="!!text">{{ text }}</span>
</AvatarFallback> </AvatarFallback>
</AvatarRoot> </AvatarRoot>

View File

@ -1,6 +1,6 @@
<template> <template>
<HoverCardRoot :open-delay="delay"> <HoverCardRoot :open-delay="delay">
<HoverCardTrigger class="inline-block cursor-help outline-none" asChild> <HoverCardTrigger class="inline-block cursor-help outline-none">
<slot></slot> <slot></slot>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardPortal v-if="!disabled"> <HoverCardPortal v-if="!disabled">

View File

@ -1,13 +1,17 @@
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite"; import { BunSQLiteDatabase, drizzle } from "drizzle-orm/bun-sqlite";
import * as schema from '../db/schema'; import * as schema from '../db/schema';
let instance: BunSQLiteDatabase<typeof schema>;
export default function useDatabase() export default function useDatabase()
{ {
const sqlite = new Database(useRuntimeConfig().database); if(!instance)
const db = drizzle({ client: sqlite, schema }); {
const sqlite = new Database(useRuntimeConfig().database);
instance = drizzle({ client: sqlite, schema });
db.run("PRAGMA journal_mode = WAL;"); instance.run("PRAGMA journal_mode = WAL;");
}
return db; return instance;
} }

Binary file not shown.

Binary file not shown.

View File

@ -12,8 +12,8 @@
</div> </div>
<div class="flex items-center px-2"> <div class="flex items-center px-2">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip> <Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
<Tooltip message="Se connecter" side="right"> <Tooltip :message="loggedIn ? 'Mon profil' : 'Se connecter'" side="right">
<NuxtLink class="" :to="{ path: '/user/login', force: true }"> <NuxtLink class="" :to="{ path: '/user/profile', force: true }">
<div class="hover:border-opacity-70 flex"> <div class="hover:border-opacity-70 flex">
<Icon icon="radix-icons:person" class="w-7 h-7 p-1" /> <Icon icon="radix-icons:person" class="w-7 h-7 p-1" />
</div> </div>
@ -31,8 +31,8 @@
</NuxtLink> </NuxtLink>
<div class="flex gap-4 items-center"> <div class="flex gap-4 items-center">
<Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip> <Tooltip message="Changer de theme" side="left"><ThemeSwitch /></Tooltip>
<Tooltip message="Se connecter" side="right"> <Tooltip :message="loggedIn ? 'Mon profil' : 'Se connecter'" side="right">
<NuxtLink class="" :to="{ path: '/user/login', force: true }"> <NuxtLink class="" :to="{ path: '/user/profile', force: true }">
<div class="bg-light-20 dark:bg-dark-20 hover:border-opacity-70 flex border p-px border-light-50 dark:border-dark-50"> <div class="bg-light-20 dark:bg-dark-20 hover:border-opacity-70 flex border p-px border-light-50 dark:border-dark-50">
<Icon icon="radix-icons:person" class="w-7 h-7 p-1" /> <Icon icon="radix-icons:person" class="w-7 h-7 p-1" />
</div> </div>
@ -56,4 +56,5 @@
import { Icon } from '@iconify/vue/dist/iconify.js'; import { Icon } from '@iconify/vue/dist/iconify.js';
const open = ref(true); const open = ref(true);
const { loggedIn } = useUserSession();
</script> </script>

22
middleware/auth.global.ts Normal file
View File

@ -0,0 +1,22 @@
export default defineNuxtRouteMiddleware(async (to, from) => {
const { loggedIn, ready, fetch } = useUserSession();
const meta = to.meta;
if(!ready)
await fetch();
if(!!meta.guestsGoesTo && !loggedIn.value)
{
return navigateTo(meta.guestsGoesTo);
}
else if(meta.requireAuth && !loggedIn.value)
{
return abortNavigation();
}
else if(!!meta.usersGoesTo && loggedIn.value)
{
return navigateTo(meta.usersGoesTo);
}
return;
});

View File

@ -2,6 +2,10 @@
"name": "d-any", "name": "d-any",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": {
"predev": "bun i && bunx nuxi cleanup",
"dev": "bunx --bun nuxi dev"
},
"dependencies": { "dependencies": {
"@iconify/vue": "^4.1.2", "@iconify/vue": "^4.1.2",
"@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/color-mode": "^3.5.2",

View File

@ -23,6 +23,7 @@ import { Icon } from '@iconify/vue/dist/iconify.js';
definePageMeta({ definePageMeta({
layout: 'login', layout: 'login',
usersGoesTo: '/user/profile',
}); });
const { add: addToast, clear: clearToasts } = useToast(); const { add: addToast, clear: clearToasts } = useToast();

View File

@ -1,9 +1,28 @@
<script setup lang="ts"> <script setup lang="ts">
const { user } = useUserSession(); definePageMeta({
guestsGoesTo: '/user/login',
})
let { user, clear } = useUserSession();
</script> </script>
<template> <template>
<Head> <Head>
<Title>Mon profil</Title> <Title>Mon profil</Title>
</Head> </Head>
<div class="flex w-full items-start py-8 gap-6" v-if="user">
<div class="flex gap-4 min-w-1/3 max-w-2/3 border border-light-35 dark:border-dark-35 p-4">
<Avatar icon="radix-icons:person" :src="`/users/${user?.id}.medium.jpg`" class="w-32 h-32" />
<div class="flex flex-col items-start">
<ProseH5>{{ user.username }}</ProseH5>
<ProseH5>{{ user.email }}</ProseH5>
<HoverCard>
<template v-slot:default><div class="border-light-red dark:border-dark-red bg-light-red bg-opacity-25 dark:bg-dark-red dark:bg-opacity-25 text-light-red dark:text-dark-red py-1 px-3" v-if="user.state === 0">Votre adresse mail n'as pas encore été validée. <Button class="ms-4">Renvoyez un mail</Button></div></template>
<template v-slot:content><span>Tant que votre adresse mail n'as pas été validée, vous n'avez que des droits de lecture.</span></template>
</HoverCard>
</div>
</div>
<div class="flex-1">
<Button @click="async () => await clear()">Se deconnecter</Button>
</div>
</div>
</template> </template>

View File

@ -39,6 +39,7 @@ import { Icon } from '@iconify/vue/dist/iconify.js';
definePageMeta({ definePageMeta({
layout: 'login', layout: 'login',
usersGoesTo: '/user/profile',
}); });
const state = reactive<Registration>({ const state = reactive<Registration>({

View File

@ -83,7 +83,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
return { success: false, error: new Error('Données utilisateur introuvable') }; return { success: false, error: new Error('Données utilisateur introuvable') };
} }
const data = await logSession(e, await setUserSession(e, { const data = logSession(e, await setUserSession(e, {
user: { user: {
...user.data, ...user.data,
email: user.email, email: user.email,

View File

@ -1,6 +1,6 @@
import useDatabase from "~/composables/useDatabase"; import useDatabase from "~/composables/useDatabase";
import { userSessionsTable as sessions } from "~/db/schema"; import { userSessionsTable } from "~/db/schema";
import { eq, and } from "drizzle-orm"; import { eq, and, sql } from "drizzle-orm";
const monthAsMs = 60 * 60 * 24 * 30 * 1000; const monthAsMs = 60 * 60 * 24 * 30 * 1000;
@ -8,12 +8,7 @@ export default defineNitroPlugin(() => {
const db = useDatabase(); const db = useDatabase();
sessionHooks.hook('fetch', async (session, event) => { sessionHooks.hook('fetch', async (session, event) => {
const result = await db.query.userSessionsTable.findFirst({ const result = db.select({ timestamp: userSessionsTable.timestamp }).from(userSessionsTable).where(and(eq(userSessionsTable.id, sql.placeholder('id')), eq(userSessionsTable.user_id, sql.placeholder('user_id')))).prepare().get({ id: session.id, user_id: session.user.id });
columns: {
timestamp: true,
},
where: and(eq(sessions.id, session.id as unknown as number), eq(sessions.user_id, session.user.id))
});
if(!result) if(!result)
{ {
@ -27,9 +22,9 @@ export default defineNitroPlugin(() => {
} }
else else
{ {
await db.update(sessions).set({ await db.update(userSessionsTable).set({
timestamp: new Date(), timestamp: new Date(),
}).where(and(eq(sessions.id, session.id as unknown as number), eq(sessions.user_id, session.user.id))); }).where(and(eq(userSessionsTable.id, sql.placeholder('id')), eq(userSessionsTable.user_id, sql.placeholder('user_id')))).prepare().run({ id: session.id, user_id: session.user.id });
} }
}); });
sessionHooks.hook('clear', async (session, event) => { sessionHooks.hook('clear', async (session, event) => {
@ -37,7 +32,7 @@ export default defineNitroPlugin(() => {
{ {
try try
{ {
await db.delete(sessions).where(and(eq(sessions.id, session.id as unknown as number), eq(sessions.user_id, session.user.id))); db.delete(userSessionsTable).where(and(eq(userSessionsTable.id, sql.placeholder('id')), eq(userSessionsTable.user_id, sql.placeholder('user_id')))).prepare().run({ id: session.id, user_id: session.user.id });
} }
catch(e) { } catch(e) { }
} }

View File

@ -29,6 +29,7 @@ export function logSession(e: H3Event<EventRequestHandler>, session: UserSession
{ {
const db = useDatabase(); const db = useDatabase();
db.insert(userSessionsTable).values({ id: sql.placeholder('id'), user_id: sql.placeholder('user_id'), timestamp: sql.placeholder('timestamp') }).prepare().execute({ id: session.id, user_id: session.user.id, timestamp: new Date()}); console.log("Logging session %s", session.id)
db.insert(userSessionsTable).values({ id: sql.placeholder('id'), user_id: sql.placeholder('user_id'), timestamp: sql.placeholder('timestamp') }).prepare().run({ id: session.id, user_id: session.user.id, timestamp: new Date() });
return session; return session;
} }