Merge branch 'main' into update-bdd
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

pull/3/head
Clément FRÉVILLE 10 months ago
commit 3dabedd89b

@ -0,0 +1,4 @@
node_modules
Dockerfile
.dockerignore
*.js

@ -0,0 +1,14 @@
FROM node:22-bookworm AS runner
WORKDIR /app
ENV NODE_ENV="production"
COPY package.json package-lock.json ./
RUN npm install --omit=dev
FROM runner AS builder
COPY . .
RUN npm install && npm run build && npm prune --omit=dev
FROM runner
COPY --from=builder /app/dist /app
EXPOSE 3000
ENTRYPOINT ["node", "src/server.js"]

@ -3,22 +3,29 @@
"module": "src/server.ts",
"type": "module",
"scripts": {
"build": "tsc",
"start": "tsx src/server.ts"
"build": "tsc && tsc-alias",
"start": "tsx src/server.ts",
"fmt": "dprint fmt"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/bun": "^1.0.4",
"dprint": "^0.46.2",
"tsc-alias": "^1.8.10",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@codemirror/collab": "^6.1.1",
"@codemirror/state": "^6.4.1",
"@fastify/cookie": "^9.3.1",
"@fastify/cors": "^9.0.0",
"@fastify/session": "^10.9.0",
"@fastify/type-provider-typebox": "^4.0.0",
"@fastify/websocket": "^10.0.1",
"@sinclair/typebox": "^0.32.9",
"dprint": "^0.46.1",
"bcrypt": "^5.1.1",
"fastify": "^4.27.0",
"nanoid": "^5.0.4",
"sqlite3": "^5.1.7",

@ -4,6 +4,11 @@ import sqlite3 from "sqlite3";
const dbDirectory = "./src/db";
const dbFilePath = `${dbDirectory}/database.db`;
export type error = {
errno: number;
code: string;
};
/* Fonction pour exécuter une requête sur la base de données */
/* Fonction pour exécuter une requête de modification de la base de données (INSERT, UPDATE, DELETE) */
@ -45,9 +50,9 @@ export function getDB<T>(
db: sqlite3.Database,
query: string,
params: any[],
): Promise<T[]> {
): Promise<T> {
return new Promise((resolve, reject) => {
db.get(query, params, (err, row: any) => {
db.get(query, params, (err, row: T) => {
if (err) {
reject(err);
} else {
@ -118,18 +123,48 @@ export function createRegisteredUserTable(db: sqlite3.Database): Promise<void> {
}
/* Insérer un utilisateur dans la table registered_user */
export function insertUser(
export async function insertUser(
db: sqlite3.Database,
login: string,
email: string,
password: string,
permissions: number,
) {
): Promise<boolean> {
const insertUserQuery = `INSERT INTO registered_user (login, email, password, permissions) VALUES (?, ?, ?, ?)`;
try {
await runDB(db, insertUserQuery, [login, email, password, permissions]);
return true;
} catch (e) {
const error = e as error;
if (error.code === "SQLITE_CONSTRAINT") {
return false;
} else {
throw e;
}
}
}
return runDB(db, insertUserQuery, [login, email, password, permissions]);
/* Vérifier si un utilisateur existe dans la table registered_user */
export async function verifyUser(
db: sqlite3.Database,
login: string,
): Promise<User | null> {
const verifyUserQuery = `SELECT login, password FROM registered_user WHERE login = ?`;
const res = await getDB<User>(db, verifyUserQuery, [login]);
if (!res) {
return null;
} else {
return res;
}
}
export type User = {
login: string;
password: string;
};
/* Modifier le login d'un utilisateur dans la table registered_user */
export function updateUserLogin(
db: sqlite3.Database,

@ -1,31 +1,30 @@
import { rebaseUpdates, Update } from "@codemirror/collab";
import { ChangeSet, Text } from "@codemirror/state";
import cors from "@fastify/cors";
import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
import websocket, { WebSocket } from "@fastify/websocket";
import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
import Fastify, { FastifyReply } from "fastify";
import { nanoid } from "nanoid";
import { allocateBuffer, getRunner } from "runner";
import { allocateBuffer, getRunner } from "./runner";
import { Pull, Push } from "zeromq";
import { ChangeSet, Text } from "@codemirror/state";
import { Update, rebaseUpdates } from "@codemirror/collab";
import * as db from "./database";
import bcrypt from "bcrypt";
import { fastifySession } from "@fastify/session";
import { fastifyCookie } from "@fastify/cookie";
const sender = new Push();
await sender.bind(`tcp://127.0.0.1:5557`);
const receiver = new Pull();
await receiver.bind(`tcp://127.0.0.1:5558`);
const clients: Record<string, FastifyReply> = {};
const generateId = () => nanoid(32);
//let updates: Update[] = [];
//let doc = Text.of(["foo"]);
type Room = {
sockets: WebSocket[];
updates: Update[];
doc: Text;
}
};
const rooms: Record<string, Room> = {};
@ -43,29 +42,47 @@ const fastify = Fastify({
type Fastify = typeof fastify;
await fastify.register(cors, {
origin: process.env.ALLOW_ORIGIN || "*",
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
});
fastify.register(fastifyCookie);
fastify.register(fastifySession, {
secret: "8jYuS75JZuxb6C72nDtH2cY6hnV4B7i35r5c39gQ3h9G9DApAweBsQ47dU9DGpk5",
cookie: {
secure: false,
sameSite: "none",
partitioned: true,
},
saveUninitialized: false,
cookieName: "session-id",
});
fastify.register(websocket);
declare module "fastify" {
interface Session {
userKey: string | null;
}
}
fastify.register(async function (fastify: Fastify) {
fastify.register(websocket);
fastify.register(async function(fastify: Fastify) {
fastify.get(
"/live/:roomId",
{
schema: {
params: Type.Object({
roomId: Type.String(),
})
params: Type.Object({
roomId: Type.String(),
}),
},
websocket: true
websocket: true,
},
(socket, request) => {
const { roomId } = request.params;
let room = rooms[roomId];
if(!room){
if (!room) {
room = {
sockets: [],
updates: [],
doc: Text.of([''])
doc: Text.of([""]),
};
rooms[roomId] = room;
}
@ -102,16 +119,33 @@ fastify.register(async function (fastify: Fastify) {
send(socket, requestId, { version: room.updates.length, doc: room.doc.toString() });
}
});
});
})
},
);
});
/* Route pour créer une room */
fastify.post(
"/live",
async (request, reply) => {
return generateId();
fastify.post("/live", {
schema: {
body: Type.Object({
code: Type.String(),
}),
},
);
}, (request, reply) => {
const { code } = request.body;
let room, roomId;
do {
roomId = generateId();
room = rooms[roomId];
} while (room);
room = {
sockets: [],
updates: [],
doc: Text.of([code]),
};
rooms[roomId] = room;
return roomId;
});
fastify.post("/run", {
schema: {
@ -168,12 +202,58 @@ fastify.post(
},
},
async (request, reply) => {
const { login, email, password, permissions } = request.body;
db.insertUser(database, login, email, password, permissions);
reply.send({ success: true });
const { login, email, password, permissions } = request.body;
// Hashage du mot de passe
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
const success = await db.insertUser(
database,
login,
email,
hashedPassword,
permissions
);
reply.send({ success });
}
);
/* Route pour vérifier si un utilisateur existe */
fastify.post(
"/users/login",
{
schema: {
body: Type.Object({
login: Type.String(),
password: Type.String(),
}),
},
},
async (request, reply) => {
const { login, password } = request.body;
const user = await db.verifyUser(database, login);
if (user === null || !(await bcrypt.compare(password, user.password))) {
reply.send({ success: false });
} else {
request.session.userKey = generateId();
reply.send({ success: true });
}
bcrypt.compare(password, user!.password)
.then(res => reply.send({ sucess: res }))
.catch(err => reply.send({ sucess: false }));
},
);
/* Route pour se déconnecter */
fastify.get("/users/logout", async (request, reply) => {
console.log(request.session.userKey);
request.session.destroy();
reply.send({ success: true });
});
/* Route pour mettre à jour le login d'un utilisateur */
fastify.put(
"/users/:id/login",
@ -194,7 +274,7 @@ fastify.put(
const { newLogin } = request.body;
db.updateUserLogin(database, id, newLogin);
reply.send({ success: true });
},
}
);
/* Route pour mettre à jour le mot de passe d'un utilisateur */
@ -215,9 +295,13 @@ fastify.put(
async (request, reply) => {
const { id } = request.params;
const { newPassword } = request.body;
db.updateUserPassword(database, id, newPassword);
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(newPassword, saltRounds);
await db.updateUserPassword(database, id, hashedPassword);
reply.send({ success: true });
},
}
);
/* Route pour mettre à jour les permissions d'un utilisateur */
@ -240,7 +324,7 @@ fastify.put(
const { newPermissions } = request.body;
await db.updateUserPermissions(database, id, newPermissions);
reply.send({ success: true });
},
}
);
/* Route pour mettre à jour l'email d'un utilisateur */
@ -289,7 +373,7 @@ fastify.delete(
const { id } = request.params;
await db.deleteUserById(database, id);
reply.send({ success: true });
},
}
);
/* Route pour supprimer un utilisateur par son login */
@ -306,7 +390,7 @@ fastify.delete(
const { login } = request.params;
await db.deleteUserByLogin(database, login);
reply.send({ success: true });
},
}
);
/* Route pour supprimer tous les utilisateurs */
@ -335,9 +419,11 @@ fastify.get(
},
async (request, reply) => {
const { id } = request.params;
const user = await db.selectUserById(database, id);
reply.send(user);
},
if (request.session.userKey) {
const user = await db.selectUserById(database, id);
reply.send(user);
}
}
);
/* Route pour récupérer un utilisateur par son login */
@ -354,7 +440,7 @@ fastify.get(
const { login } = request.params;
const user = await db.selectUserByLogin(database, login);
reply.send(user);
},
}
);
/* Route pour créer un work */
@ -375,7 +461,7 @@ fastify.post(
const { id_user, link, language, title, code } = request.body;
await db.insertWork(database, link, id_user, language, title, code);
reply.send({ success: true });
},
}
);
/* Route pour récupérer tous les works */
@ -406,7 +492,7 @@ fastify.delete(
const { id } = request.params;
db.deleteWork(database, id);
reply.send({ success: true });
},
}
);
/* Route pour récupérer un work par son Link */
@ -445,55 +531,6 @@ fastify.get(
},
);
/* Update the work title by its ID */
fastify.put(
"/works/:id/title",
{
schema: {
params: Type.Object({
id: Type.Number({
minimum: 0,
}),
}),
body: Type.Object({
newTitle: Type.String(),
}),
},
},
async (request, reply) => {
const { id } = request.params;
const { newTitle } = request.body;
await db.updateWorkTitle(database, id, newTitle);
reply.send({ success: true });
}
);
/* Update the work content by its ID */
fastify.put(
"/works/:id/content",
{
schema: {
params: Type.Object({
id: Type.Number({
minimum: 0,
}),
}),
body: Type.Object({
newContent: Type.String(),
language: Type.String(),
}),
},
},
async (request, reply) => {
const { id } = request.params;
const { newContent, language } = request.body;
await db.updateWorkContent(database, id, newContent, language);
reply.send({ success: true });
}
);
/* Forward output est une fonction asynchrone qui permet de récupérer les messages envoyés par le container et de les renvoyer au client */
async function forwardOutput() {
for await (const [buff] of receiver) {
@ -531,4 +568,4 @@ async function forwardOutput() {
}
/* Lancer le serveur et la fonction forwardOutput sur le même thread en parallèle */
await Promise.all([fastify.listen({ port: 3000 }), forwardOutput()]);
await Promise.all([fastify.listen({ port: 3000, host: '0.0.0.0' }), forwardOutput()]);

@ -1,15 +1,15 @@
{
"compilerOptions": {
"baseUrl": "./src",
"outDir": "./dist",
"types": ["bun-types"],
"lib": ["esnext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"noEmit": true,
"allowImportingTsExtensions": true,
"moduleResolution": "Node",
"noEmit": false,
"moduleDetection": "force",
"strict": true,
@ -18,5 +18,8 @@
"composite": true,
"downlevelIteration": true,
"allowSyntheticDefaultImports": true
},
"tsc-alias": {
"resolveFullPaths": true
}
}

Loading…
Cancel
Save