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:
parent
a2b896916e
commit
c5dbfcad6e
17 changed files with 270 additions and 102 deletions
9
Makefile
9
Makefile
|
|
@ -6,7 +6,7 @@
|
||||||
# By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ #
|
# By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ #
|
||||||
# +#+#+#+#+#+ +#+ #
|
# +#+#+#+#+#+ +#+ #
|
||||||
# Created: 2023/11/12 11:05:05 by rparodi #+# #+# #
|
# 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@build:
|
||||||
npm --prefix=./src/ run 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
|
||||||
.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
|
||||||
|
|
|
||||||
12
flake.nix
12
flake.nix
|
|
@ -1,13 +1,20 @@
|
||||||
{
|
{
|
||||||
description = "Flake utils demo";
|
description = "Flake utils demo";
|
||||||
|
|
||||||
inputs.nixpkgs.url = "github:nixos/nixpkgs";
|
inputs = {
|
||||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
nixpkgs.url = "github:nixos/nixpkgs";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
dbmlSQLite = {
|
||||||
|
url = "github:maix0/DBML_SQLite";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
flake-utils,
|
flake-utils,
|
||||||
|
dbmlSQLite,
|
||||||
}:
|
}:
|
||||||
flake-utils.lib.eachDefaultSystem (
|
flake-utils.lib.eachDefaultSystem (
|
||||||
system: let
|
system: let
|
||||||
|
|
@ -21,6 +28,7 @@
|
||||||
nodejs_24
|
nodejs_24
|
||||||
pnpm
|
pnpm
|
||||||
typescript
|
typescript
|
||||||
|
dbmlSQLite.packages.${system}.default
|
||||||
|
|
||||||
# allow building better-sqlite3
|
# allow building better-sqlite3
|
||||||
clang
|
clang
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,32 @@
|
||||||
-- this file will make sure that the database is always up to date with the correct schema
|
CREATE TABLE IF NOT EXISTS user (
|
||||||
-- when editing this file, make sure to always include stuff like `IF NOT EXISTS` such as to not throw error
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
-- NEVER DROP ANYTHING IN THIS FILE
|
name TEXT NOT NULL UNIQUE,
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
password TEXT
|
||||||
id STRING UNIQUE PRIMARY KEY, -- UUIDv7 as a string
|
);
|
||||||
name STRING UNIQUE, -- name of the user
|
CREATE TABLE IF NOT EXISTS auth (
|
||||||
token STRING UNIQUE, -- the token of the user (aka the cookie)
|
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
2
src/auth/.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
37
src/auth/package.json
Normal file
37
src/auth/package.json
Normal 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
45
src/auth/src/app.ts
Normal 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 }
|
||||||
16
src/auth/src/plugins/README.md
Normal file
16
src/auth/src/plugins/README.md
Normal 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/).
|
||||||
11
src/auth/src/plugins/sensible.ts
Normal file
11
src/auth/src/plugins/sensible.ts
Normal 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)
|
||||||
|
})
|
||||||
49
src/auth/src/routes/set.ts
Normal file
49
src/auth/src/routes/set.ts
Normal 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
30
src/auth/src/run.ts
Normal 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
5
src/auth/tsconfig.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
26
src/auth/vite.config.js
Normal file
26
src/auth/vite.config.js
Normal 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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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')
|
|
||||||
})
|
|
||||||
|
|
@ -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')
|
|
||||||
})
|
|
||||||
|
|
@ -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 })
|
|
||||||
})
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"noEmit": false
|
|
||||||
},
|
|
||||||
"include": ["../src/**/*.ts", "**/*.ts"]
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue