update(wip): I want to docker commit myself

This commit is contained in:
Maieul BOYER 2025-07-28 18:02:01 +02:00
parent f9671ea198
commit 68f8d87477
43 changed files with 557 additions and 2915 deletions

15
src/@shared/README.md Normal file
View file

@ -0,0 +1,15 @@
# Utils Library
This library is made to be shared by all of the service.
It should handle things like database interface, shared stuff like 'make sure this is accessed by an connected user'
# How it is used
Painfully.
# Why no Docker ?
Docker compose can't make "build-only" docker images, where we just use them.
So we have to "build" the library in every Dockerfile for every service.
Well not really, dockers caches things for use,
meaning that while it seems that everybody builds it, it is only built once

30
src/@shared/package.json Normal file
View file

@ -0,0 +1,30 @@
{
"type": "module",
"name": "shared",
"private": true,
"version": "1.0.0",
"description": "shared utils library",
"scripts": {
"embed": "npm run embed:sql",
"embed:sql": "node scripts/embed:sql.js",
"build:ts": "npm run embed:sql && tsc -d",
"watch:ts": "tsc -d -w"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"better-sqlite3": "^11.10.0",
"fastify": "^5.0.0",
"fastify-plugin": "^5.0.1",
"typescript-result": "3.1.1",
"uuidv7": "^1.0.2"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.1.0",
"c8": "^10.1.2",
"concurrently": "^9.0.0",
"typescript": "~5.8.2"
}
}

View file

@ -0,0 +1,56 @@
// ************************************************************************** //
// //
// ::: :::::::: //
// embed.js :+: :+: :+: //
// +:+ +:+ +:+ //
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
// +#+#+#+#+#+ +#+ //
// Created: 2025/06/19 23:32:59 by maiboyer #+# #+# //
// Updated: 2025/06/20 00:09:04 by maiboyer ### ########.fr //
// //
// ************************************************************************** //
import { readFile, writeFile, stat } from "node:fs/promises";
/**
* escape a string to be a valid js string literal
* @param {string} input
* @returns {string}
*/
function escape(input) {
return JSON.stringify(input)
.replace('\n', '\\n')
.replace('\t', '\\t')
.replace('\r', '\\r')
.replace('\v', '\\v');
}
/**
* @description Embed {input} inside a default exported string at location {output}
* @param {string} input
* @param {string} output
* @returns void
*/
export default async function embed(input, output) {
const inputData = (await readFile(input)).toString('utf-8');
const inputStat = await stat(input);
const escapedData = escape(inputData);
const fullFile = `\
//! this file was generated automatically.
//! it is just a string literal that is the file ${input}
//! if you want to edit this file, DONT. edit ${input} please
//!
//! this file need to be regenerated on changes to ${input} manually.
//! the \`npm run build:ts\` might regenerate it, but do check.
//! here is the date of the last time it was generated: ${new Date(Date.now())}
//! the file ${input} that is embeded was modified on ${inputStat.mtime}
//! the file ${input} that is embeded was ${inputStat.size} bytes
export default ${escapedData};\
`;
await writeFile(output, fullFile, { flush: true, flag: "w" })
}

View file

@ -0,0 +1,15 @@
// ************************************************************************** //
// //
// ::: :::::::: //
// embed:sql.js :+: :+: :+: //
// +:+ +:+ +:+ //
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
// +#+#+#+#+#+ +#+ //
// Created: 2025/06/19 23:30:39 by maiboyer #+# #+# //
// Updated: 2025/07/28 15:36:11 by maiboyer ### ########.fr //
// //
// ************************************************************************** //
import embed from "./embed.js";
await embed('./src/database/init.sql', './src/database/init.sql.ts')

View file

@ -0,0 +1,105 @@
// ************************************************************************** //
// //
// ::: :::::::: //
// index.ts :+: :+: :+: //
// +:+ +:+ +:+ //
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
// +#+#+#+#+#+ +#+ //
// Created: 2025/07/28 17:36:22 by maiboyer #+# #+# //
// Updated: 2025/07/28 17:36:26 by maiboyer ### ########.fr //
// //
// ************************************************************************** //
import fp from 'fastify-plugin'
import { FastifyInstance } from 'fastify'
import sqlite from 'better-sqlite3'
import { Result } from 'typescript-result'
import initSql from "./init.sql.js"
import { newUUIDv7, UUIDv7 } from '@shared/uuid'
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 {
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) {
this.db = sqlite(db_path, {});
this.db.pragma('journal_mode = WAL');
this.db.transaction(() => this.db.exec(initSql))();
}
/**
* close the database
*/
destroy(): void {
// remove any statement from the cache
this.st.clear();
// close the database
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
declare module 'fastify' {
export interface FastifyInstance {
db: Database;
}
}
export type DatabaseOption = {
path: string;
};
export const uDatabase = fp<DatabaseOption>(async function(
_fastify: FastifyInstance,
_options: DatabaseOption) {
});
export default uDatabase;

View file

@ -0,0 +1,57 @@
Project Transcendance {
Note: '''
# DBML for Transcendance
DBML (database markup language) is a simple, readable DSL language designed to define database structures.
## Benefits
* It is simple, flexible and highly human-readable
* It is database agnostic, focusing on the essential database structure definition without worrying about the detailed syntaxes of each database
* Comes with a free, simple database visualiser at [dbdiagram.io](http://dbdiagram.io)
# how to use it
ask Maieul :)
'''
}
Table user {
id integer [PK, not null, increment]
name text [unique, not null]
password text [null, Note: "If password is NULL, this means that the user is created through OAUTH2"]
Note: "Represent a user"
}
Table auth {
id integer [PK, not null, increment]
provider integer [ref: > provider.id, not null]
user integer [ref: > user.id, not null]
oauth2_user text [not null, unique, Note: '''
This makes sure that an oauth2 login is the always the same `user`
Aka can't have two account bound to the same <OAUTH2> account
''']
}
Table provider {
id integer [PK, not null, increment]
name text [PK, not null]
displayName text [not null]
secret text [not null]
token_url text [not null]
auth_url text [not null]
me_url text [not null]
Note: "Oauth2 Providers"
}
Table session {
id integer [PK, not null, increment]
cookie text [PK, unique, not null]
userid integer [ref: > user.id, not null]
createAt text [not null]
userAgent text [not null]
reason integer [null, ref: > provider.id]
Note: "Every session for users"
}

View file

@ -0,0 +1,9 @@
-- 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
-- NEVER DROP ANYTHING IN THIS FILE
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)
);

View file

@ -0,0 +1,12 @@
//! this file was generated automatically.
//! it is just a string literal that is the file ./src/database/init.sql
//! if you want to edit this file, DONT. edit ./src/database/init.sql please
//!
//! this file need to be regenerated on changes to ./src/database/init.sql manually.
//! the `npm run build:ts` might regenerate it, but do check.
//! here is the date of the last time it was generated: Mon Jul 28 2025 16:40:15 GMT+0200 (Central European Summer Time)
//! the file ./src/database/init.sql that is embeded was modified on Sat Jul 19 2025 15:33:56 GMT+0200 (Central European Summer Time)
//! the file ./src/database/init.sql that is embeded was 436 bytes
export default "-- this file will make sure that the database is always up to date with the correct schema\n-- when editing this file, make sure to always include stuff like `IF NOT EXISTS` such as to not throw error\n-- NEVER DROP ANYTHING IN THIS FILE\nCREATE TABLE IF NOT EXISTS users (\n id STRING UNIQUE PRIMARY KEY, -- UUIDv7 as a string\n name STRING UNIQUE, -- name of the user\n token STRING UNIQUE, -- the token of the user (aka the cookie)\n);\n\n";

View file

@ -0,0 +1,49 @@
// ************************************************************************** //
// //
// ::: :::::::: //
// index.ts :+: :+: :+: //
// +:+ +:+ +:+ //
// By: maiboyer <maiboyer@student.42.fr> +#+ +:+ +#+ //
// +#+#+#+#+#+ +#+ //
// Created: 2025/06/20 17:41:01 by maiboyer #+# #+# //
// Updated: 2025/07/28 15:42:53 by maiboyer ### ########.fr //
// //
// ************************************************************************** //
import { Result } from "typescript-result";
import { uuidv7 } from "uuidv7";
export class InvalidUUID extends Error {
public readonly type = 'invalid-uuid';
};
// A UUID is a all lowercase string that looks like this:
// `xxxxxxxx-xxxx-7xxx-xxx-xxxxxxxxxxxx`
// where x is any hex number
//
// it is a unique identifier, where when created can be assumed to be unique
// (aka no checks are needed)
//
// this uses the v7 of UUID, which means that every uuid is part random,
// part based on the timestamp it was Created
//
// This allows better ergonomics as you can "see" which uuid are older
// and which one are newer.
// This also makes sure that random UUID don't collide (unless you go back in time...).
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.toLowerCase() as UUIDv7);
}
export function newUUIDv7(): UUIDv7 {
return uuidv7() as UUIDv7;
}

View file

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}