Add insertion and deletion of tables for db sync, and user view

This commit is contained in:
Peaceultime 2024-09-10 14:25:23 +02:00
parent fa1a13d411
commit e904f28b3b
14 changed files with 127 additions and 23 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -76,7 +76,7 @@ async function debounced()
<div class="cursor-pointer hover:bg-light-25 dark:hover:bg-dark-25 px-4 py-1 " v-for="result of results.users" :key="result.id" <div class="cursor-pointer hover:bg-light-25 dark:hover:bg-dark-25 px-4 py-1 " v-for="result of results.users" :key="result.id"
@mouseenter="(e) => (e.target as HTMLElement).classList.add('is-selected')" @mouseenter="(e) => (e.target as HTMLElement).classList.add('is-selected')"
@mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')" @mouseleave="(e) => (e.target as HTMLElement).classList.remove('is-selected')"
@mousedown.prevent="navigateTo(`/user/${result.id}`); input = ''; emit('navigate');"> @mousedown.prevent="navigateTo(`/users/${result.id}`); input = ''; emit('navigate');">
<div class=""> <div class="">
<Highlight class="text-lg" :text="result.username" :matched="input" /> <Highlight class="text-lg" :text="result.username" :matched="input" />
</div> </div>

BIN
db.sqlite

Binary file not shown.

View File

@ -26,22 +26,41 @@ export default defineNuxtModule({
const db = new Database(nuxt.options.runtimeConfig.dbFile); const db = new Database(nuxt.options.runtimeConfig.dbFile);
db.exec(`PRAGMA foreign_keys = 0`); db.exec(`PRAGMA foreign_keys = 0`);
const oldSchema = db.query(`SELECT * FROM sqlite_schema WHERE type = 'table'`).all() as Schema[];
const structure = db.query(`SELECT * FROM pragma_table_info(?1)`); const structure = db.query(`SELECT * FROM pragma_table_info(?1)`);
(db.transaction((tables: Schema[]) => { (db.transaction((tables: Schema[], oldTables: Schema[]) => {
for(const table of tables) for(const table of tables)
{ {
const oldIdx = oldTables.findIndex(e => e.name === table.name);
if(table.name === 'sqlite_sequence') if(table.name === 'sqlite_sequence')
{
oldTables.splice(oldIdx, 1);
continue; continue;
}
const columns = structure.all(table.name) as Structure[]; const columns = structure.all(table.name) as Structure[];
db.exec(`ALTER TABLE ${table.name} RENAME TO ${table.name}_old`); if(oldIdx !== -1)
db.exec(table.sql); {
db.exec(`INSERT INTO ${table.name} (${columns.map(e => `"${e.name}"`).join(', ')}) SELECT * FROM ${table.name}_old`); oldTables.splice(oldIdx, 1);
db.exec(`DROP TABLE ${table.name}_old`);
db.exec(`ALTER TABLE ${table.name} RENAME TO ${table.name}_old`);
db.exec(table.sql);
db.exec(`INSERT INTO ${table.name} (${columns.map(e => `"${e.name}"`).join(', ')}) SELECT * FROM ${table.name}_old`);
db.exec(`DROP TABLE ${table.name}_old`);
}
else
{
db.exec(table.sql);
}
} }
})).immediate(schema); for(const table of oldTables)
{
db.exec(`DROP TABLE ${table.name}`);
}
})).immediate(schema, oldSchema);
db.exec(`PRAGMA foreign_keys = 1`); db.exec(`PRAGMA foreign_keys = 1`);

View File

@ -2,13 +2,8 @@
<Head> <Head>
<Title>Inconnu</Title> <Title>Inconnu</Title>
</Head> </Head>
<div class="site-body-center-column"> <div class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
<div class="render-container"> <div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Introuvable</div>
<div class="not-found-container"> <div class="text-lg text-light-60 dark:text-dark-60">Cette page n'existe pas</div>
<div class="not-found-image"></div>
<div class="not-found-title">Introuvable</div>
<div class="not-found-description">Cette page n'existe pas</div>
</div>
</div>
</div> </div>
</template> </template>

View File

@ -1,6 +1,31 @@
<script setup lang="ts">
const route = useRoute();
const { data: user } = useFetch(`/api/users/${route.params.id}`);
const { data: projects } = useFetch(`/api/users/${route.params.id}/projects`);
const { data: comments } = useFetch(`/api/users/${route.params.id}/comments`);
</script>
<template> <template>
<Head> <Head>
<Title>Inconnu</Title> <Title>Inconnu</Title>
</Head> </Head>
<div>TODO :)</div> <div v-if="user" class="border border-light-35 dark:border-dark-35 p-4 flex">
<div>
<picture :width=128 :height=128 class="flex" >
<source :src="`/users/${user?.id}/normal.jpg`" :width=128 :height=128 />
<Icon :icon="`users/unknown`" :width=128 :height=128 ></Icon>
</picture>
</div>
<div class="flex flex-col">
<span class="text-xl font-semibold">{{ user?.username }}</span>
<span>Inscrit depuis le {{ format(new Date(user.signin_timestamp), 'dd/MM/yyyy') }}</span>
<br/>
<span>A créé {{ projects?.length ?? 0 }} projet(s)</span>
<span>A créé {{ comments?.length ?? 0 }} commentaire(s)</span>
</div>
</div>
<div v-else class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Introuvable</div>
<div class="text-lg text-light-60 dark:text-dark-60">Cette page n'existe pas</div>
</div>
</template> </template>

View File

@ -62,13 +62,13 @@ export default defineEventHandler(async (e): Promise<Return> => {
{ {
const hash = await Bun.password.hash(body.data.password); const hash = await Bun.password.hash(body.data.password);
const registration = db.query(`INSERT INTO users(username, email, hash, state) VALUES(?1, ?2, ?3, 0)`); const registration = db.query(`INSERT INTO users(username, email, hash, state) VALUES(?1, ?2, ?3, 0)`);
registration.get(body.data.username, body.data.email, hash) as any; registration.run(body.data.username, body.data.email, hash);
const userIdQuery = db.query(`SELECT id FROM users WHERE username = ?1`); const userIdQuery = db.query(`SELECT id FROM users WHERE username = ?1`);
const id = (userIdQuery.get(body.data.username) as any).id; const id = (userIdQuery.get(body.data.username) as any).id;
const registeringData = db.query(`INSERT INTO users_data(user_id) VALUES(?1)`); const registeringData = db.query(`INSERT INTO users_data(user_id, signin_timestamp) VALUES(?1, ?2)`);
registeringData.get(id); registeringData.run(id, Date.now());
logSession(e, await setUserSession(e, { user: { id: id, username: body.data.username, email: body.data.email, state: 0 } }) as UserSessionRequired); logSession(e, await setUserSession(e, { user: { id: id, username: body.data.username, email: body.data.email, state: 0 } }) as UserSessionRequired);

View File

@ -12,5 +12,5 @@ export default defineEventHandler((e) => {
const db = useDatabase(); const db = useDatabase();
return db.query(`SELECT id, usernamme, email, state FROM users WHERE id = ?1`).get(id) as User; return db.query(`SELECT id, username, email, state, d.* FROM users u LEFT JOIN users_data d ON u.id = d.user_id WHERE u.id = ?1`).get(id) as User;
}); });

View File

@ -0,0 +1,16 @@
import useDatabase from "~/composables/useDatabase";
import type { Comment } from "~/types/auth";
export default defineEventHandler((e) => {
const id = getRouterParam(e, 'id');
if(!id)
{
setResponseStatus(e, 400);
return;
}
const db = useDatabase();
return db.query(`SELECT * FROM explorer_comments WHERE user_id = ?1`).all(id) as Comment[];
});

View File

@ -0,0 +1,16 @@
import useDatabase from "~/composables/useDatabase";
import type { Project } from "~/types/api";
export default defineEventHandler((e) => {
const id = getRouterParam(e, 'id');
if(!id)
{
setResponseStatus(e, 400);
return;
}
const db = useDatabase();
return db.query(`SELECT * FROM explorer_projects WHERE owner = ?1`).all(id) as Project[];
});

View File

@ -11,10 +11,12 @@ export default defineNitroPlugin(() => {
if(!result) if(!result)
{ {
clearUserSession(event);
throw createError({ statusCode: 401, message: 'Unauthorized' }); throw createError({ statusCode: 401, message: 'Unauthorized' });
} }
else if(result && result.lastRefresh && result.lastRefresh < Date.now() - monthAsMs) else if(result && result.lastRefresh && result.lastRefresh < Date.now() - monthAsMs)
{ {
clearUserSession(event);
throw createError({ statusCode: 401, message: 'Session has expired' }); throw createError({ statusCode: 401, message: 'Session has expired' });
} }
else else
@ -25,8 +27,12 @@ export default defineNitroPlugin(() => {
sessionHooks.hook('clear', async (session, event) => { sessionHooks.hook('clear', async (session, event) => {
if(session.id && session.user) if(session.id && session.user)
{ {
const query = db.prepare('DELETE FROM user_sessions WHERE id = ?1 AND user_id = ?2'); try
query.run(session.id, session.user.id); {
const query = db.prepare('DELETE FROM user_sessions WHERE id = ?1 AND user_id = ?2');
query.run(session.id, session.user.id);
}
catch(e) { }
} }
}); });
}); });

Binary file not shown.

2
types/auth.d.ts vendored
View File

@ -28,7 +28,7 @@ export interface UserRawData {
} }
export interface UserExtendedData { export interface UserExtendedData {
signin_timestamp: number;
} }
export type User = UserRawData & UserExtendedData; export type User = UserRawData & UserExtendedData;

View File

@ -5,4 +5,31 @@ export function unifySlug(slug: string | string[]): string
export function parseId(id: string | undefined): string |undefined export function parseId(id: string | undefined): string |undefined
{ {
return id?.normalize('NFD')?.replace(/[\u0300-\u036f]/g, '')?.replace(/^\d\. */g, '')?.replace(/\s/g, "-")?.replace(/%/g, "-percent")?.replace(/\?/g, "-q")?.toLowerCase(); return id?.normalize('NFD')?.replace(/[\u0300-\u036f]/g, '')?.replace(/^\d\. */g, '')?.replace(/\s/g, "-")?.replace(/%/g, "-percent")?.replace(/\?/g, "-q")?.toLowerCase();
}
export function padLeft(text: string, pad: string, length: number): string
{
return text.concat(pad.repeat(length - text.length));
}
export function padRight(text: string, pad: string, length: number): string
{
return pad.repeat(length - text.length).concat(text);
}
export function format(date: Date, template: string): string
{
const transforms = {
"yyyy": (date: Date) => date.getUTCFullYear().toString(),
"MM": (date: Date) => padRight((date.getUTCMonth() + 1).toString(), '0', 2),
"dd": (date: Date) => padRight(date.getUTCDate().toString(), '0', 2),
"mm": (date: Date) => padRight(date.getFullYear().toString(), '0', 2),
"HH": (date: Date) => padRight(date.getFullYear().toString(), '0', 2),
"ss": (date: Date) => padRight(date.getFullYear().toString(), '0', 2),
};
const keys = Object.keys(transforms);
for(const key of keys)
{
template = template.replaceAll(key, () => transforms[key](date));
}
return template;
} }