|
|
|
@ -1,567 +1,90 @@
|
|
|
|
|
import cors from "@fastify/cors";
|
|
|
|
|
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 { 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";
|
|
|
|
|
import http from 'http';
|
|
|
|
|
import { nanoid } from 'nanoid';
|
|
|
|
|
import { Pull, Push } from 'zeromq';
|
|
|
|
|
|
|
|
|
|
const host = 'localhost';
|
|
|
|
|
const port = 3000;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
type Room = {
|
|
|
|
|
sockets: WebSocket[];
|
|
|
|
|
updates: Update[];
|
|
|
|
|
doc: Text;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rooms: Record<string, Room> = {};
|
|
|
|
|
|
|
|
|
|
function send(socket: WebSocket, requestId: number, payload: unknown) {
|
|
|
|
|
const response = {
|
|
|
|
|
_request: requestId,
|
|
|
|
|
payload,
|
|
|
|
|
};
|
|
|
|
|
socket.send(JSON.stringify(response));
|
|
|
|
|
}
|
|
|
|
|
const clients: Record<string, http.ServerResponse> = {};
|
|
|
|
|
|
|
|
|
|
const fastify = Fastify({
|
|
|
|
|
logger: true,
|
|
|
|
|
}).withTypeProvider<TypeBoxTypeProvider>();
|
|
|
|
|
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",
|
|
|
|
|
});
|
|
|
|
|
const CORS = {
|
|
|
|
|
'Access-Control-Allow-Methods': '*',
|
|
|
|
|
'Access-Control-Allow-Headers': '*',
|
|
|
|
|
'Access-Control-Allow-Origin': process.env.ALLOW_ORIGIN || '*',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
declare module "fastify" {
|
|
|
|
|
interface Session {
|
|
|
|
|
userKey: number;
|
|
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
|
|
|
res.writeHead(200, CORS);
|
|
|
|
|
res.end();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fastify.register(websocket);
|
|
|
|
|
fastify.register(async function(fastify: Fastify) {
|
|
|
|
|
fastify.get(
|
|
|
|
|
"/live/:roomId",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
roomId: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
websocket: true,
|
|
|
|
|
},
|
|
|
|
|
(socket, request) => {
|
|
|
|
|
const { roomId } = request.params;
|
|
|
|
|
let room = rooms[roomId];
|
|
|
|
|
if (!room) {
|
|
|
|
|
room = {
|
|
|
|
|
sockets: [],
|
|
|
|
|
updates: [],
|
|
|
|
|
doc: Text.of([""]),
|
|
|
|
|
};
|
|
|
|
|
rooms[roomId] = room;
|
|
|
|
|
}
|
|
|
|
|
room.sockets.push(socket);
|
|
|
|
|
socket.on("message", message => {
|
|
|
|
|
const data = JSON.parse(message.toString());
|
|
|
|
|
const requestId = data._request;
|
|
|
|
|
|
|
|
|
|
if (data.type === "pullUpdates") {
|
|
|
|
|
send(socket, requestId, room.updates.slice(data.version));
|
|
|
|
|
} else if (data.type === "pushUpdates") {
|
|
|
|
|
let received = data.updates.map((json: any) => ({
|
|
|
|
|
clientID: json.clientID,
|
|
|
|
|
changes: ChangeSet.fromJSON(json.changes),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (data.version != room.updates.length) {
|
|
|
|
|
received = rebaseUpdates(received, room.updates.slice(data.version));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let update of received) {
|
|
|
|
|
room.updates.push(update);
|
|
|
|
|
room.doc = update.changes.apply(room.doc);
|
|
|
|
|
}
|
|
|
|
|
send(
|
|
|
|
|
socket,
|
|
|
|
|
requestId,
|
|
|
|
|
received.map((update: any) => ({
|
|
|
|
|
clientID: update.clientID,
|
|
|
|
|
changes: update.changes.toJSON(),
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
} else if (data.type === "getDocument") {
|
|
|
|
|
send(socket, requestId, { version: room.updates.length, doc: room.doc.toString() });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Route pour créer une room */
|
|
|
|
|
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: {
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
code: Type.String(),
|
|
|
|
|
language: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
}, (req, reply) => {
|
|
|
|
|
const { code, language } = req.body;
|
|
|
|
|
const runner = getRunner(language);
|
|
|
|
|
if (runner === null) {
|
|
|
|
|
return reply.status(422).send({ error: "Invalid language" });
|
|
|
|
|
}
|
|
|
|
|
const jobId = generateId();
|
|
|
|
|
const buffer = allocateBuffer(jobId, code, runner);
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Création du répertoire de la base de données s'il n'existe pas */
|
|
|
|
|
db.createDbDirectory();
|
|
|
|
|
|
|
|
|
|
/* Ouvrir la base de données */
|
|
|
|
|
const database = db.openDatabase();
|
|
|
|
|
|
|
|
|
|
/* Créer les tables si elles n'existent pas */
|
|
|
|
|
db.createTables(database);
|
|
|
|
|
|
|
|
|
|
/* Route pour créer un utilisateur */
|
|
|
|
|
fastify.post(
|
|
|
|
|
"/users",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
login: Type.String(),
|
|
|
|
|
email: Type.String(),
|
|
|
|
|
password: Type.String(),
|
|
|
|
|
permissions: Type.Number(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
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 });
|
|
|
|
|
let body: Uint8Array[] = [];
|
|
|
|
|
|
|
|
|
|
switch (req.url) {
|
|
|
|
|
case '/run':
|
|
|
|
|
req
|
|
|
|
|
.on('data', chunk => {
|
|
|
|
|
body.push(chunk);
|
|
|
|
|
})
|
|
|
|
|
.on('end', () => {
|
|
|
|
|
let params = JSON.parse(Buffer.concat(body).toString());
|
|
|
|
|
|
|
|
|
|
const code = params.code;
|
|
|
|
|
const image = 'ghcr.io/moshell-lang/moshell:master';
|
|
|
|
|
const buffer = Buffer.allocUnsafe(jobId.length + image.length + code.length + 8);
|
|
|
|
|
buffer.write(jobId, 0);
|
|
|
|
|
buffer.writeUInt32BE(image.length, jobId.length);
|
|
|
|
|
buffer.writeUInt32BE(code.length, jobId.length + 4);
|
|
|
|
|
buffer.write(image, jobId.length + 8);
|
|
|
|
|
buffer.write(code, jobId.length + 8 + image.length);
|
|
|
|
|
res.writeHead(200, {
|
|
|
|
|
'Content-Type': 'text/event-stream',
|
|
|
|
|
Connection: 'keep-alive',
|
|
|
|
|
'Cache-Control': 'no-cache',
|
|
|
|
|
...CORS,
|
|
|
|
|
});
|
|
|
|
|
sender.send(buffer).then(() => {
|
|
|
|
|
res.write('event: connected\n');
|
|
|
|
|
res.write(`data: ${jobId}\n`);
|
|
|
|
|
res.write('id: 0\n\n');
|
|
|
|
|
});
|
|
|
|
|
//res.end(body);
|
|
|
|
|
})
|
|
|
|
|
.on('close', () => {
|
|
|
|
|
res.end('OK');
|
|
|
|
|
delete clients[jobId];
|
|
|
|
|
});
|
|
|
|
|
clients[jobId] = res;
|
|
|
|
|
|
|
|
|
|
//res.end()
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
res.writeHead(404, CORS);
|
|
|
|
|
res.end('404!');
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* 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 = user.id_user;
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour se déconnecter */
|
|
|
|
|
fastify.get("/users/logout", async (request, reply) => {
|
|
|
|
|
console.log(request.session.userKey);
|
|
|
|
|
await request.session.destroy();
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Route pour mettre à jour le login d'un utilisateur */
|
|
|
|
|
fastify.put(
|
|
|
|
|
"/users/:id/login",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
newLogin: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
const { newLogin } = request.body;
|
|
|
|
|
db.updateUserLogin(database, id, newLogin);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour mettre à jour le mot de passe d'un utilisateur */
|
|
|
|
|
fastify.put(
|
|
|
|
|
"/users/:id/password",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
newPassword: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
const { newPassword } = request.body;
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
fastify.put(
|
|
|
|
|
"/users/:id/permissions",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
newPermissions: Type.Number(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
const { newPermissions } = request.body;
|
|
|
|
|
await db.updateUserPermissions(database, id, newPermissions);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour mettre à jour l'email d'un utilisateur */
|
|
|
|
|
fastify.put(
|
|
|
|
|
"/users/:id/email",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
newEmail: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
const { newEmail } = request.body;
|
|
|
|
|
|
|
|
|
|
// Check if the ID relates to an existing ID.
|
|
|
|
|
const user = await db.selectUserById(database, id);
|
|
|
|
|
if (!user) {
|
|
|
|
|
reply.status(404).send({ error: "User not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await db.updateUserEmail(database, id, newEmail);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour supprimer un utilisateur par son ID */
|
|
|
|
|
fastify.delete(
|
|
|
|
|
"/users/:id",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
await db.deleteUserById(database, id);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour supprimer un utilisateur par son login */
|
|
|
|
|
fastify.delete(
|
|
|
|
|
"/users/login/:login",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
login: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { login } = request.params;
|
|
|
|
|
await db.deleteUserByLogin(database, login);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour supprimer tous les utilisateurs */
|
|
|
|
|
fastify.delete("/users", async (request, reply) => {
|
|
|
|
|
await db.deleteAllUsers(database);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Route pour récupérer tous les utilisateurs */
|
|
|
|
|
fastify.get("/users", async (request, reply) => {
|
|
|
|
|
const users = await db.selectAllUsers(database);
|
|
|
|
|
reply.send(users);
|
|
|
|
|
server.listen(port, () => {
|
|
|
|
|
console.log(`Server is running on http://${host}:${port}`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Route pour récupérer un utilisateur par son ID */
|
|
|
|
|
fastify.get(
|
|
|
|
|
"/users/:id",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
if (request.session.userKey) {
|
|
|
|
|
const user = await db.selectUserById(database, id);
|
|
|
|
|
reply.send(user);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour récupérer un utilisateur par son login */
|
|
|
|
|
fastify.get(
|
|
|
|
|
"/users/login/:login",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
login: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { login } = request.params;
|
|
|
|
|
const user = await db.selectUserByLogin(database, login);
|
|
|
|
|
reply.send(user);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour créer un work */
|
|
|
|
|
fastify.post(
|
|
|
|
|
"/works",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
body: Type.Object({
|
|
|
|
|
language: Type.String(),
|
|
|
|
|
title: Type.String(),
|
|
|
|
|
code: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const link = generateId();
|
|
|
|
|
const user = request.session.userKey;
|
|
|
|
|
const { language, title, code } = request.body;
|
|
|
|
|
await db.insertWork(database, link, user, language, title, code);
|
|
|
|
|
reply.send({ success: true, link });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour récupérer tous les works */
|
|
|
|
|
fastify.get("/works", async (request, reply) => {
|
|
|
|
|
const works = await db.selectAllWorks(database);
|
|
|
|
|
reply.send(works);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Route pour supprimer tous les works */
|
|
|
|
|
fastify.delete("/works", async (request, reply) => {
|
|
|
|
|
await db.deleteAllWorks(database);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Route pour supprimer un work par son ID */
|
|
|
|
|
fastify.delete(
|
|
|
|
|
"/works/:id",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
db.deleteWork(database, id);
|
|
|
|
|
reply.send({ success: true });
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour récupérer un work par son Link */
|
|
|
|
|
fastify.get(
|
|
|
|
|
"/works/:link",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
link: Type.String(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const {link} = request.params;
|
|
|
|
|
const work = await db.selectWorkByLink(database, link);
|
|
|
|
|
reply.send(work);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* Route pour récupérer le dernier work par l'id de l'utilisateur */
|
|
|
|
|
fastify.get(
|
|
|
|
|
"/works/last-work/:user_id",
|
|
|
|
|
{
|
|
|
|
|
schema: {
|
|
|
|
|
params: Type.Object({
|
|
|
|
|
user_id: Type.Number({
|
|
|
|
|
minimum: 0,
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const { user_id } = request.params;
|
|
|
|
|
const work = await db.selectLastWorkByUserId(database, user_id);
|
|
|
|
|
reply.send(work);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/* 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) {
|
|
|
|
|
const messageType = buff.readInt8();
|
|
|
|
|
const jobId = buff.subarray(1, 33).toString();
|
|
|
|
|
const reply = clients[jobId];
|
|
|
|
|
if (!reply) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
console.debug(`Forwarding message type ${messageType} for job ${jobId}`);
|
|
|
|
|
const raw = reply.raw;
|
|
|
|
|
switch (messageType) {
|
|
|
|
|
case 1:
|
|
|
|
|
case 2:
|
|
|
|
|
const text = encodeURIComponent(buff.subarray(33).toString());
|
|
|
|
|
raw.write("event: message\n");
|
|
|
|
|
if (messageType === 1) {
|
|
|
|
|
raw.write("id: stdout\n");
|
|
|
|
|
} else {
|
|
|
|
|
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(`data: ${exitCode}\n\n`);
|
|
|
|
|
raw.end();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
console.error("Unknown message type", messageType);
|
|
|
|
|
}
|
|
|
|
|
for await (const [buff] of receiver) {
|
|
|
|
|
const jobId = buff.subarray(0, 32).toString();
|
|
|
|
|
console.log(`Received ${jobId}`);
|
|
|
|
|
const res = clients[jobId];
|
|
|
|
|
if (!res) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
res.write('event: message\n');
|
|
|
|
|
res.write(`data: ${encodeURIComponent(buff.subarray(32).toString())}\n`);
|
|
|
|
|
res.write('id: 1\n\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Lancer le serveur et la fonction forwardOutput sur le même thread en parallèle */
|
|
|
|
|
await Promise.all([fastify.listen({ port: 3000, host: '0.0.0.0' }), forwardOutput()]);
|
|
|
|
|