feat(database): split stuff into multiple files
This commit is contained in:
parent
baf9dc54c6
commit
70d72f4419
9 changed files with 307 additions and 76 deletions
|
|
@ -9,6 +9,7 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/jwt": "^9.1.0",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"fastify": "^5.0.0",
|
||||
"fastify-plugin": "^5.0.1",
|
||||
|
|
|
|||
11
src/@shared/src/auth/index.ts
Normal file
11
src/@shared/src/auth/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import fastifyJwt from "@fastify/jwt";
|
||||
import { FastifyPluginAsync } from "fastify";
|
||||
import fp from 'fastify-plugin'
|
||||
|
||||
export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||
let env = process.env.JWT_SECRET;
|
||||
if (env === undefined || env === null)
|
||||
throw "JWT_SECRET is not defined"
|
||||
void fastify.register(fastifyJwt, { secret: env });
|
||||
});
|
||||
|
||||
|
|
@ -1,77 +1,14 @@
|
|||
import fp from 'fastify-plugin'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import sqlite from 'better-sqlite3'
|
||||
|
||||
// @ts-ignore: this file is included using vite, typescript doesn't know how to include this...
|
||||
import initSql from "./init.sql?raw"
|
||||
import { Base } from "./mixin/_base";
|
||||
import { UserDb } from "./mixin/user";
|
||||
import { SessionDb } from "./mixin/session";
|
||||
|
||||
/**
|
||||
* represent a unique user (by its ID.)
|
||||
* Having this means that the user does exist (aka it hasn't been deleted)
|
||||
*/
|
||||
export type UserID = number & { readonly __brand: unique symbol };
|
||||
/**
|
||||
* The full representation of an user
|
||||
*
|
||||
* @property id [UserID]: The id of the user (unique)
|
||||
* @property name [string]: The username of the user (unique)
|
||||
* @property password [?string]: The password hash of the user (if password is defined)
|
||||
*/
|
||||
export type DbUser = {
|
||||
readonly id: UserID,
|
||||
readonly name: string,
|
||||
readonly password: string | null,
|
||||
};
|
||||
|
||||
// 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))();
|
||||
class Database extends UserDb(SessionDb(Base as any)) {
|
||||
constructor(path: string) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 getUser(user: UserID): DbUser {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
|
|
@ -90,6 +27,7 @@ export const useDatabase = fp<DatabaseOption>(async function(
|
|||
_options: DatabaseOption) {
|
||||
f.log.info("Database has been hooked up to fastify ?!");
|
||||
f.log.warn("TODO: actually hook up database to fastify...");
|
||||
f.decorate('db', new Database(_options.path));
|
||||
});
|
||||
|
||||
export default useDatabase;
|
||||
|
|
|
|||
64
src/@shared/src/database/mixin/_base.ts
Normal file
64
src/@shared/src/database/mixin/_base.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import sqlite from "better-sqlite3";
|
||||
|
||||
// @ts-ignore: this file is included using vite, typescript doesn't know how to include this...
|
||||
import initSql from "../init.sql?raw"
|
||||
|
||||
export type MixinBase<T = {}> = new (...args: any[]) => {
|
||||
constructor(db_path: string): void,
|
||||
destroy(): void,
|
||||
prepare(s: string): sqlite.Statement,
|
||||
db: sqlite.Database,
|
||||
} & T;
|
||||
|
||||
|
||||
// Only way to use the database. Everything must be done through this.
|
||||
// Prefer to use prepared statement `using this.db.prepare`
|
||||
//
|
||||
// this is the base, meaning that no actual query are made from this file.
|
||||
// To create a new query function, go open another file, create a class that inherit from this class
|
||||
// in the `index.ts` file, import the new class, and make the `Database` class inherit it
|
||||
export class Base {
|
||||
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) {
|
||||
console.log("NEW DB :)");
|
||||
this.db = sqlite(db_path, {});
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
this.db.transaction(() => this.db.exec(initSql))();
|
||||
}
|
||||
|
||||
/**
|
||||
* close the database
|
||||
*/
|
||||
public 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 ?')
|
||||
*/
|
||||
protected 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;
|
||||
}
|
||||
}
|
||||
21
src/@shared/src/database/mixin/_template.ts
Normal file
21
src/@shared/src/database/mixin/_template.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//import sqlite from "better-sqlite3"
|
||||
import { MixinBase } from "./_base"
|
||||
|
||||
// never use this directly
|
||||
export const TemplateDb = function <TBase extends MixinBase>(Base: TBase) {
|
||||
return class extends Base {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length != 1 && !(args[0] instanceof String))
|
||||
throw "Invalid arguments to mixing class"
|
||||
super(args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type TemplateId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type TemplateType = {
|
||||
readonly id: TemplateId,
|
||||
readonly field: string,
|
||||
readonly field2: number,
|
||||
};
|
||||
27
src/@shared/src/database/mixin/session.ts
Normal file
27
src/@shared/src/database/mixin/session.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//import sqlite from "better-sqlite3"
|
||||
import { MixinBase } from "./_base"
|
||||
|
||||
// never use this directly
|
||||
|
||||
export const SessionDb = function <TBase extends MixinBase>(Base: TBase) {
|
||||
return class extends Base {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length != 1 && !(args[0] instanceof String))
|
||||
throw "Invalid arguments to mixing class"
|
||||
super(args[0]);
|
||||
}
|
||||
|
||||
public getSessionFromId(id: SessionId): Session | null {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type Session = {
|
||||
readonly id: SessionId,
|
||||
readonly name: string,
|
||||
readonly salt: string,
|
||||
readonly password: string,
|
||||
};
|
||||
35
src/@shared/src/database/mixin/user.ts
Normal file
35
src/@shared/src/database/mixin/user.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//import sqlite from "better-sqlite3"
|
||||
import { MixinBase } from "./_base"
|
||||
|
||||
// never use this directly
|
||||
|
||||
|
||||
export const UserDb = function <TBase extends MixinBase>(Base: TBase) {
|
||||
return class extends Base {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length != 1 && !(args[0] instanceof String))
|
||||
throw "Invalid arguments to mixing class"
|
||||
super(args[0]);
|
||||
}
|
||||
|
||||
private userFromRow(row: any): User {
|
||||
throw "TODO: User from Row"
|
||||
}
|
||||
|
||||
public getUser(id: UserId): User | null {
|
||||
return null
|
||||
}
|
||||
public setUser(id: UserId, partialUser: Partial<Omit<User, 'id'>>): User | null {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type UserId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type User = {
|
||||
readonly id: UserId,
|
||||
readonly name: string,
|
||||
readonly salt: string,
|
||||
readonly password: string,
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Database } from "@shared/database";
|
||||
|
||||
export type UserID = Number & { readonly __brand: unique symbol };
|
||||
|
||||
export async function getUser(this: Database, id: UserID) {
|
||||
console.log(this);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue