update(wip): I want to docker commit myself
This commit is contained in:
parent
f9671ea198
commit
68f8d87477
43 changed files with 557 additions and 2915 deletions
15
src/@shared/README.md
Normal file
15
src/@shared/README.md
Normal 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
30
src/@shared/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
56
src/@shared/scripts/embed.js
Normal file
56
src/@shared/scripts/embed.js
Normal 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" })
|
||||
}
|
||||
15
src/@shared/scripts/embed:sql.js
Normal file
15
src/@shared/scripts/embed:sql.js
Normal 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')
|
||||
105
src/@shared/src/database/index.ts
Normal file
105
src/@shared/src/database/index.ts
Normal 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;
|
||||
|
||||
57
src/@shared/src/database/init.dbml
Normal file
57
src/@shared/src/database/init.dbml
Normal 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"
|
||||
}
|
||||
9
src/@shared/src/database/init.sql
Normal file
9
src/@shared/src/database/init.sql
Normal 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)
|
||||
);
|
||||
|
||||
12
src/@shared/src/database/init.sql.ts
Normal file
12
src/@shared/src/database/init.sql.ts
Normal 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";
|
||||
49
src/@shared/src/uuid/index.ts
Normal file
49
src/@shared/src/uuid/index.ts
Normal 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;
|
||||
}
|
||||
8
src/@shared/tsconfig.json
Normal file
8
src/@shared/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue