feat(auth): initialize service

- Make rule `sql`: convert .dbml file to .sql file
- Removed tests: not used...
- Added dbml_sqlite to flake.nix
This commit is contained in:
Maieul BOYER 2025-08-02 20:48:30 +02:00 committed by Maix0
parent a2b896916e
commit c5dbfcad6e
17 changed files with 270 additions and 102 deletions

View file

@ -6,7 +6,7 @@
# By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2023/11/12 11:05:05 by rparodi #+# #+# #
# Updated: 2025/07/28 17:49:54 by maiboyer ### ########.fr #
# Updated: 2025/08/02 20:48:04 by maiboyer ### ########.fr #
# #
# **************************************************************************** #
@ -119,5 +119,10 @@ npm@fclean: npm@clean
npm@build:
npm --prefix=./src/ run build
# this convert the .dbml file to an actual sql file that SQLite can handle :)
sql:
@echo "if the command isn't found, contact maieul :)"
dbml_sqlite -t -f -w ./src/@shared/src/database/init.sql ./src/@shared/src/database/init.dbml
# phony
.PHONY: all clean fclean re header footer npm_install npm_clear
.PHONY: all clean fclean re header footer npm@install npm@clean npm@fclean npm@build sql

View file

@ -1,13 +1,20 @@
{
description = "Flake utils demo";
inputs.nixpkgs.url = "github:nixos/nixpkgs";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
dbmlSQLite = {
url = "github:maix0/DBML_SQLite";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
flake-utils,
dbmlSQLite,
}:
flake-utils.lib.eachDefaultSystem (
system: let
@ -21,7 +28,8 @@
nodejs_24
pnpm
typescript
dbmlSQLite.packages.${system}.default
# allow building better-sqlite3
clang
];

View file

@ -1,9 +1,32 @@
-- 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)
CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL UNIQUE,
password TEXT
);
CREATE TABLE IF NOT EXISTS auth (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
provider INTEGER NOT NULL,
user INTEGER NOT NULL,
oauth2_user TEXT NOT NULL UNIQUE,
FOREIGN KEY(provider) REFERENCES provider(id),
FOREIGN KEY(user) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS provider (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT PRIMARY KEY 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
);
CREATE TABLE IF NOT EXISTS session (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
cookie TEXT PRIMARY KEY NOT NULL UNIQUE,
userid INTEGER NOT NULL,
createAt TEXT NOT NULL,
userAgent TEXT NOT NULL,
reason INTEGER,
FOREIGN KEY(userid) REFERENCES user(id),
FOREIGN KEY(reason) REFERENCES provider(id)
);

2
src/auth/.dockerignore Normal file
View file

@ -0,0 +1,2 @@
/dist
/node_modules

37
src/auth/package.json Normal file
View file

@ -0,0 +1,37 @@
{
"type": "module",
"private": false,
"name": "auth",
"version": "1.0.0",
"description": "This project was bootstrapped with Fastify-CLI.",
"main": "app.ts",
"directories": {
"test": "test"
},
"scripts": {
"start": "npm run build && node dist/run.js",
"build": "vite build",
"build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@fastify/autoload": "^6.3.1",
"@fastify/formbody": "^8.0.2",
"@fastify/multipart": "^9.0.3",
"@fastify/sensible": "^6.0.0",
"@fastify/static": "^8.2.0",
"fastify": "^5.0.0",
"fastify-cli": "^7.4.0",
"fastify-plugin": "^5.0.0",
"raw-body": "^3.0.0",
"sharp": "^0.34.2"
},
"devDependencies": {
"@types/node": "^22.1.0",
"rollup-plugin-node-externals": "^8.0.1",
"vite": "^7.0.6",
"vite-tsconfig-paths": "^5.1.4"
}
}

45
src/auth/src/app.ts Normal file
View file

@ -0,0 +1,45 @@
import { FastifyPluginAsync } from 'fastify'
import fastifyFormBody from '@fastify/formbody'
import fastifyMultipart from '@fastify/multipart'
import { mkdir } from 'node:fs/promises'
import fp from 'fastify-plugin'
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
const routes = import.meta.glob('./routes/**/*.ts', { eager: true });
// When using .decorate you have to specify added properties for Typescript
declare module 'fastify' {
export interface FastifyInstance {
image_store: string;
}
}
const app: FastifyPluginAsync = async (
fastify,
opts
): Promise<void> => {
// Place here your custom code!
for (const plugin of Object.values(plugins)) {
void fastify.register(plugin, {});
}
for (const route of Object.values(routes)) {
void fastify.register(route, {});
}
//void fastify.register(MyPlugin, {})
void fastify.register(fastifyFormBody, {})
void fastify.register(fastifyMultipart, {})
// The use of fastify-plugin is required to be able
// to export the decorators to the outer scope
void fastify.register(fp(async (fastify) => {
const image_store = process.env.USER_ICONS_STORE ?? "/tmp/icons";
fastify.decorate('image_store', image_store)
await mkdir(fastify.image_store, { recursive: true })
}))
}
export default app
export { app }

View file

@ -0,0 +1,16 @@
# Plugins Folder
Plugins define behavior that is common to all the routes in your
application. Authentication, caching, templates, and all the other cross
cutting concerns should be handled by plugins placed in this folder.
Files in this folder are typically defined through the
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module,
making them non-encapsulated. They can define decorators and set hooks
that will then be used in the rest of your application.
Check out:
* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/)
* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/).
* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/).

View file

@ -0,0 +1,11 @@
import fp from 'fastify-plugin'
import sensible, { FastifySensibleOptions } from '@fastify/sensible'
/**
* This plugins adds some utilities to handle http errors
*
* @see https://github.com/fastify/fastify-sensible
*/
export default fp<FastifySensibleOptions>(async (fastify) => {
fastify.register(sensible)
})

View file

@ -0,0 +1,49 @@
import { FastifyPluginAsync } from 'fastify'
import { join } from 'node:path'
import { open } from 'node:fs/promises'
import sharp from 'sharp'
import { newUUIDv7 } from '@shared/uuid'
import rawBody from 'raw-body'
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
// await fastify.register(authMethod, {});
// here we register plugins that will be active for the current fastify instance (aka everything in this function)
// we register a route handler for: `/<USERID_HERE>`
// it sets some configuration options, and set the actual function that will handle the request
fastify.addContentTypeParser('*', function(request, payload, done) {
done()
});
fastify.post('/:userid', async function(request, reply) {
let buffer = await rawBody(request.raw);
// this is how we get the `:userid` part of things
const userid: string | undefined = (request.params as any)['userid'];
if (userid === undefined) {
return await reply.code(403);
}
const image_store: string = fastify.getDecorator('image_store')
const image_path = join(image_store, userid)
try {
let img = sharp(buffer);
img.resize({
height: 128,
width: 128,
fit: 'fill',
})
const data = await img.png({ compressionLevel: 6 }).toBuffer()
let image_file = await open(image_path, "w", 0o666)
await image_file.write(data);
await image_file.close()
} catch (e: any) {
fastify.log.error(`Error: ${e}`);
reply.code(400);
return { status: "error", message: e.toString() };
}
})
}
export default route

30
src/auth/src/run.ts Normal file
View file

@ -0,0 +1,30 @@
// this sould only be used by the docker file !
import fastify, { FastifyInstance } from "fastify";
import app from './app.js'
const start = async () => {
const envToLogger = {
development: {
transport: {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
production: true,
test: false,
}
const f: FastifyInstance = fastify({ logger: envToLogger.development });
try {
await f.register(app, {});
await f.listen({ port: 80, host: '0.0.0.0' })
} catch (err) {
f.log.error(err)
process.exit(1)
}
}
start()

5
src/auth/tsconfig.json Normal file
View file

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

26
src/auth/vite.config.js Normal file
View file

@ -0,0 +1,26 @@
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
import nodeExternals from 'rollup-plugin-node-externals'
import path from 'node:path'
export default defineConfig({
root: __dirname, // service root
plugins: [tsconfigPaths(), nodeExternals()],
build: {
ssr: true,
outDir: 'dist',
emptyOutDir: true,
lib: {
entry: path.resolve(__dirname, 'src/run.ts'), // adjust main entry
formats: ['cjs'], // CommonJS for Node.js
fileName: () => 'index.js',
},
rollupOptions: {
external: [],
},
target: 'node24', // or whatever Node version you use
sourcemap: true,
minify: false, // for easier debugging
}
})

View file

@ -1,43 +0,0 @@
// This file contains code that we reuse between our tests.
import helper from 'fastify-cli/helper.js'
import * as test from 'node:test'
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
export type TestContext = {
after: typeof test.after
}
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const AppPath = path.join(__dirname, '..', 'src', 'app.ts')
// Fill in this config with all the configurations
// needed for testing the application
function config () {
return {
skipOverride: true // Register our application with fastify-plugin
}
}
// Automatically build and tear down our instance
async function build (t: TestContext) {
// you can set all the options supported by the fastify CLI command
const argv = [AppPath]
// fastify-plugin ensures that all decorators
// are exposed for testing purposes, this is
// different from the production setup
const app = await helper.build(argv, config())
// Tear down our app after we are done
// eslint-disable-next-line no-void
t.after(() => void app.close())
return app
}
export {
config,
build
}

View file

@ -1,13 +0,0 @@
import { test } from 'node:test'
import * as assert from 'node:assert'
import Fastify from 'fastify'
import Support from '../../src/plugins/support.js'
test('support works standalone', async (t) => {
const fastify = Fastify()
// eslint-disable-next-line no-void
void fastify.register(Support)
await fastify.ready()
assert.equal(fastify.someSupport(), 'hugs')
})

View file

@ -1,13 +0,0 @@
import { test } from 'node:test'
import * as assert from 'node:assert'
import { build } from '../helper.js'
test('example is loaded', async (t) => {
const app = await build(t)
const res = await app.inject({
url: '/example'
})
assert.equal(res.payload, 'this is an example')
})

View file

@ -1,12 +0,0 @@
import { test } from 'node:test'
import * as assert from 'node:assert'
import { build } from '../helper.js'
test('default root route', async (t) => {
const app = await build(t)
const res = await app.inject({
url: '/'
})
assert.deepStrictEqual(JSON.parse(res.payload), { root: true })
})

View file

@ -1,8 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"noEmit": false
},
"include": ["../src/**/*.ts", "**/*.ts"]
}