update test
This commit is contained in:
parent
0b3ea4b406
commit
3f5cb97501
5 changed files with 133 additions and 19 deletions
22
src/utils/package-lock.json
generated
22
src/utils/package-lock.json
generated
|
|
@ -11,7 +11,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^11.10.0",
|
"better-sqlite3": "^11.10.0",
|
||||||
"fastify": "^5.0.0",
|
"fastify": "^5.0.0",
|
||||||
"fastify-plugin": "^5.0.1"
|
"fastify-plugin": "^5.0.1",
|
||||||
|
"typescript-result": "3.1.1",
|
||||||
|
"uuidv7": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
|
|
@ -2122,6 +2124,15 @@
|
||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript-result": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript-result/-/typescript-result-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-KQ7SdBa1UcZCu+qI7pgZ1FdzBIlGiyoHTaQQDshtsKCKiPdzLlkU6OWkJuhjzo+xl+y+dOVO+2HyyEpODNDHyw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
|
@ -2135,6 +2146,15 @@
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuidv7": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuidv7/-/uuidv7-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"uuidv7": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^11.10.0",
|
"better-sqlite3": "^11.10.0",
|
||||||
"fastify": "^5.0.0",
|
"fastify": "^5.0.0",
|
||||||
"fastify-plugin": "^5.0.1"
|
"fastify-plugin": "^5.0.1",
|
||||||
|
"typescript-result": "3.1.1",
|
||||||
|
"uuidv7": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
|
|
|
||||||
|
|
@ -6,29 +6,81 @@
|
||||||
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
|
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
|
||||||
// +#+#+#+#+#+ +#+ //
|
// +#+#+#+#+#+ +#+ //
|
||||||
// Created: 2025/06/17 17:06:31 by maiboyer #+# #+# //
|
// Created: 2025/06/17 17:06:31 by maiboyer #+# #+# //
|
||||||
// Updated: 2025/06/20 00:11:43 by maiboyer ### ########.fr //
|
// Updated: 2025/07/17 16:28:48 by maiboyer ### ########.fr //
|
||||||
// //
|
// //
|
||||||
// ************************************************************************** //
|
// ************************************************************************** //
|
||||||
|
|
||||||
import fp from 'fastify-plugin'
|
import fp from 'fastify-plugin'
|
||||||
import { FastifyInstance } from 'fastify'
|
import { FastifyInstance } from 'fastify'
|
||||||
import sqlite from 'better-sqlite3'
|
import sqlite from 'better-sqlite3'
|
||||||
|
import { Result } from 'typescript-result'
|
||||||
|
|
||||||
import initSql from "./init.sql.js"
|
import initSql from "./init.sql.js"
|
||||||
|
import { newUUIDv7, UUIDv7 } from './uuid.js'
|
||||||
|
|
||||||
|
|
||||||
|
export class DBUserExists extends Error {
|
||||||
|
public readonly type = 'db-user-exists';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only way to use the database. Everything must be done through this.
|
||||||
|
// Prefer to use prepared statement `using this.db.prepare`
|
||||||
export class Database {
|
export class Database {
|
||||||
private db: sqlite.Database;
|
private db: sqlite.Database;
|
||||||
|
private st: Map<string, sqlite.Statement> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of the database, and init it to a known state
|
||||||
|
* the file ./init.sql will be ran onto the database, creating any table that might be missing
|
||||||
|
*/
|
||||||
constructor(db_path: string) {
|
constructor(db_path: string) {
|
||||||
this.db = sqlite(db_path, {});
|
this.db = sqlite(db_path, {});
|
||||||
this.db.pragma('journal_mode = WAL');
|
this.db.pragma('journal_mode = WAL');
|
||||||
this.db.exec(initSql);
|
this.db.transaction(() => this.db.exec(initSql))();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close the database
|
||||||
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
|
// remove any statement from the cache
|
||||||
|
this.st.clear();
|
||||||
|
// close the database
|
||||||
this.db?.close();
|
this.db?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use this to create queries. This will create statements (kinda expensive) and cache them
|
||||||
|
* since they will be cached, this means that they are only created once,
|
||||||
|
* otherwise they'll be just spat out from the cache
|
||||||
|
* the statements are cached by the {query} argument,
|
||||||
|
* meaning that if you try to make two identiqual statement, but with different {query} they won't be cached
|
||||||
|
*
|
||||||
|
* @example this.prepare('SELECT * FROM users WHERE id = ?')
|
||||||
|
* @example this.prepare('SELECT * FROM users LIMIT 100 OFFSET ?')
|
||||||
|
*/
|
||||||
|
private prepare(query: string): sqlite.Statement {
|
||||||
|
let st = this.st.get(query);
|
||||||
|
if (st !== undefined) return st;
|
||||||
|
|
||||||
|
st = this.db.prepare(query);
|
||||||
|
this.st.set(query, st);
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createUser(user: string): Result<UUIDv7, DBUserExists> {
|
||||||
|
const st = this.prepare('INSERT INTO users VALUES (?, ?) RETURNING id');
|
||||||
|
|
||||||
|
try {
|
||||||
|
st.get(newUUIDv7(), user)
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
console.log(e)
|
||||||
|
console.log(typeof e)
|
||||||
|
}
|
||||||
|
return Result.ok(newUUIDv7());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When using .decorate you have to specify added properties for Typescript
|
// When using .decorate you have to specify added properties for Typescript
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
-- this file will make sure that the database is always up to date with the correct schema
|
-- this file will make sure that the database is always up to date with the correct schema
|
||||||
-- when editing this file, make sure to always include stuff like `IF NOT EXISTS` such as to not throw error
|
-- when editing this file, make sure to always include stuff like `IF NOT EXISTS` such as to not throw error
|
||||||
-- NEVER DROP ANYTHING IN THIS FILE
|
-- NEVER DROP ANYTHING IN THIS FILE
|
||||||
CREATE TABLE IF NOT EXISTS users (name STRING);
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id STRING UNIQUE PRIMARY KEY, -- UUIDv7 as a string
|
||||||
|
name STRING UNIQUE, -- name of the user
|
||||||
|
token STRING UNIQUE, -- the token of the user (aka the cookie)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
|
||||||
35
src/utils/src/uuid.ts
Normal file
35
src/utils/src/uuid.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// ************************************************************************** //
|
||||||
|
// //
|
||||||
|
// ::: :::::::: //
|
||||||
|
// uuid.ts :+: :+: :+: //
|
||||||
|
// +:+ +:+ +:+ //
|
||||||
|
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
|
||||||
|
// +#+#+#+#+#+ +#+ //
|
||||||
|
// Created: 2025/06/20 17:41:01 by maiboyer #+# #+# //
|
||||||
|
// Updated: 2025/06/20 17:44:29 by maiboyer ### ########.fr //
|
||||||
|
// //
|
||||||
|
// ************************************************************************** //
|
||||||
|
|
||||||
|
import { Result } from "typescript-result";
|
||||||
|
import { uuidv7 } from "uuidv7";
|
||||||
|
|
||||||
|
export class InvalidUUID extends Error {
|
||||||
|
public readonly type = 'invalid-uuid';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UUIDv7 = string & { readonly __brand: unique symbol };
|
||||||
|
const uuidv7Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
|
||||||
|
export function isUUIDv7(value: string): value is UUIDv7 {
|
||||||
|
return uuidv7Regex.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toUUIDv7(value: string): Result<UUIDv7, InvalidUUID> {
|
||||||
|
if (!isUUIDv7(value)) return Result.error(new InvalidUUID());
|
||||||
|
|
||||||
|
return Result.ok(value as UUIDv7);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newUUIDv7(): UUIDv7 {
|
||||||
|
return uuidv7() as UUIDv7;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue