diff --git a/package.json b/package.json index db2a021..dbeb388 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "@fastify/cors": "^9.0.0", "@fastify/type-provider-typebox": "^4.0.0", "@sinclair/typebox": "^0.32.9", - "fastify": "^4.25.2", + "fastify": "^4.27.0", + "fastify-sqlite-typed": "^0.1.1", "nanoid": "^5.0.4", "sqlite3": "^5.1.7", "zeromq": "6.0.0-beta.19" diff --git a/src/database.ts b/src/database.ts index bac6eb5..87264df 100644 --- a/src/database.ts +++ b/src/database.ts @@ -179,12 +179,19 @@ function selectUserById(db: sqlite3.Database, id: number) { }); } -/////////////////////////// Exécution des requêtes /////////////////////////// -const db = openDatabase(); -db.serialize(() => { - createDbDirectory(); - createRegisteredUserTable(db); - insertUser(db, "user1", "password1", 1); - selectAllUsers(db); - closeDatabase(db); -}); +/////////////////////////// Export des fonctions /////////////////////////// +export { + createDbDirectory, + openDatabase, + closeDatabase, + createRegisteredUserTable, + insertUser, + updateUserLogin, + updateUserPassword, + updateUserPermissions, + deleteUser, + deleteAllUsers, + selectAllUsers, + selectUserByLogin, + selectUserById, +}; diff --git a/src/server.ts b/src/server.ts index 20a926b..379bc19 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,9 +1,25 @@ -import cors from '@fastify/cors'; -import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'; -import Fastify, { FastifyReply } from 'fastify'; -import { nanoid } from 'nanoid'; -import { allocateBuffer, IMAGES } from 'runner'; -import { Pull, Push } from 'zeromq'; +import cors from "@fastify/cors"; +import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; +import Fastify, { FastifyReply, FastifyRequest } from "fastify"; +import { nanoid } from "nanoid"; +import { allocateBuffer, IMAGES } from "runner"; +import { Pull, Push } from "zeromq"; + +import { + createDbDirectory, + openDatabase, + closeDatabase, + createRegisteredUserTable, + insertUser, + updateUserLogin, + updateUserPassword, + updateUserPermissions, + deleteUser, + deleteAllUsers, + selectAllUsers, + selectUserByLogin, + selectUserById, +} from "./database"; const sender = new Push(); await sender.bind(`tcp://127.0.0.1:5557`); @@ -17,37 +33,143 @@ const fastify = Fastify({ logger: true, }).withTypeProvider(); await fastify.register(cors, { - origin: process.env.ALLOW_ORIGIN || '*', + origin: process.env.ALLOW_ORIGIN || "*", }); -fastify.post('/run', { - schema: { - body: Type.Object({ - code: Type.String(), - language: Type.String(), - }), +// Code runner in a container + +// POST /run : Run code in a container + +fastify.post( + "/run", + { + schema: { + body: Type.Object({ + code: Type.String(), + language: Type.String(), + }), + }, }, -}, (req, reply) => { - const { code, language } = req.body; - const jobId = generateId(); - const buffer = allocateBuffer(jobId, code, IMAGES.moshell); - reply.raw.writeHead(200, { - 'Content-Type': 'text/event-stream', - Connection: 'keep-alive', - 'Cache-Control': 'no-cache', - 'Access-Control-Allow-Origin': process.env.ALLOW_ORIGIN || '*', - }); - reply.raw.on('close', () => { - delete clients[jobId]; - }); - sender.send(buffer).then(() => { - reply.raw.write('event: connected\n'); - reply.raw.write(`data: ${jobId}\n`); - reply.raw.write('id: 0\n\n'); - }); - clients[jobId] = reply; + (req, reply) => { + const { code, language } = req.body; + const jobId = generateId(); + const buffer = allocateBuffer(jobId, code, IMAGES.moshell); + reply.raw.writeHead(200, { + "Content-Type": "text/event-stream", + Connection: "keep-alive", + "Cache-Control": "no-cache", + "Access-Control-Allow-Origin": process.env.ALLOW_ORIGIN || "*", + }); + reply.raw.on("close", () => { + delete clients[jobId]; + }); + sender.send(buffer).then(() => { + reply.raw.write("event: connected\n"); + reply.raw.write(`data: ${jobId}\n`); + reply.raw.write("id: 0\n\n"); + }); + clients[jobId] = reply; + } +); + +// Database + +// Création du répertoire de la base de données s'il n'existe pas +createDbDirectory(); + +// Ouvrir la base de données +const db = openDatabase(); + +// Créer la table registered_user si elle n'existe pas +createRegisteredUserTable(db); + +// Route pour créer un utilisateur +fastify.post<{ + Body: { + login: string; + password: string; + permissions: number; + }; +}>("/users", async (request, reply) => { + const { login, password, permissions } = request.body; + insertUser(db, login, password, permissions); + reply.send({ success: true }); +}); + +// Route pour mettre à jour le login d'un utilisateur +fastify.put<{ + Params: { id: string }; + Body: { newLogin: string }; +}>("/users/:id/login", async (request, reply) => { + const { id } = request.params; + const { newLogin } = request.body; + updateUserLogin(db, parseInt(id), newLogin); + reply.send({ success: true }); +}); + +// Route pour mettre à jour le mot de passe d'un utilisateur +fastify.put<{ + Params: { id: string }; + Body: { newPassword: string }; +}>("/users/:id/password", async (request, reply) => { + const { id } = request.params; + const { newPassword } = request.body; + updateUserPassword(db, parseInt(id), newPassword); + reply.send({ success: true }); }); +// Route pour mettre à jour les permissions d'un utilisateur +fastify.put<{ + Params: { id: string }; + Body: { newPermissions: number }; +}>("/users/:id/permissions", async (request, reply) => { + const { id } = request.params; + const { newPermissions } = request.body; + updateUserPermissions(db, parseInt(id), newPermissions); + reply.send({ success: true }); +}); + +// Route pour supprimer un utilisateur +fastify.delete<{ + Params: { id: string }; +}>("/users/:id", async (request, reply) => { + const { id } = request.params; + deleteUser(db, parseInt(id)); + reply.send({ success: true }); +}); + +// Route pour supprimer tous les utilisateurs +fastify.delete("/users", async (request, reply) => { + deleteAllUsers(db); + reply.send({ success: true }); +}); + +// Route pour récupérer tous les utilisateurs +fastify.get("/users", async (request, reply) => { + const users = selectAllUsers(db); + return users; +}); + +// Route pour récupérer un utilisateur par son ID +fastify.get<{ + Params: { id: string }; +}>("/users/:id", async (request, reply) => { + const { id } = request.params; + const user = selectUserById(db, parseInt(id)); + return user; +}); + +// Route pour récupérer un utilisateur par son login +fastify.get<{ + Params: { login: string }; +}>("/users/login/:login", async (request, reply) => { + const { login } = request.params; + const user = selectUserByLogin(db, login); + return user; +}); + +// Forward output from the runner to the client + async function forwardOutput() { for await (const [buff] of receiver) { const messageType = buff.readInt8(); @@ -62,23 +184,23 @@ async function forwardOutput() { case 1: case 2: const text = encodeURIComponent(buff.subarray(33).toString()); - raw.write('event: message\n'); + raw.write("event: message\n"); if (messageType === 1) { - raw.write('id: stdout\n'); + raw.write("id: stdout\n"); } else { - raw.write('id: stderr\n'); + raw.write("id: stderr\n"); } raw.write(`data: ${text}\n\n`); break; case 3: const exitCode = buff.readUint32BE(33); - raw.write('event: message\n'); - raw.write('id: exit\n'); + raw.write("event: message\n"); + raw.write("id: exit\n"); raw.write(`data: ${exitCode}\n\n`); raw.end(); break; default: - console.error('Unknown message type', messageType); + console.error("Unknown message type", messageType); } } }