Implement private user rooms (#4)
continuous-integration/drone/push Build is passing Details

Co-authored-by: bastien ollier <bastien.ollier@etu.uca.fr>
Reviewed-on: #4
Reviewed-by: Clément FRÉVILLE <clement.freville2@etu.uca.fr>
Co-authored-by: Bastien OLLIER <bastien.ollier@noreply.codefirst.iut.uca.fr>
Co-committed-by: Bastien OLLIER <bastien.ollier@noreply.codefirst.iut.uca.fr>
pull/5/head
Bastien OLLIER 11 months ago committed by Clément FRÉVILLE
parent ef726bc789
commit 85e5d9257e

@ -18,6 +18,7 @@
"@fastify/type-provider-typebox": "^4.0.0", "@fastify/type-provider-typebox": "^4.0.0",
"@fastify/websocket": "^10.0.1", "@fastify/websocket": "^10.0.1",
"@sinclair/typebox": "^0.32.9", "@sinclair/typebox": "^0.32.9",
"dprint": "^0.46.1",
"fastify": "^4.27.0", "fastify": "^4.27.0",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",

@ -10,7 +10,7 @@ const dbFilePath = `${dbDirectory}/database.db`;
export function runDB( export function runDB(
db: sqlite3.Database, db: sqlite3.Database,
query: string, query: string,
params: any[] params: any[],
): Promise<void> { ): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
db.run(query, params, (err) => { db.run(query, params, (err) => {
@ -26,7 +26,7 @@ export function runDB(
/* Fonction pour récupérer plusieurs lignes de la base de données */ /* Fonction pour récupérer plusieurs lignes de la base de données */
export function allDB<T>( export function allDB<T>(
db: sqlite3.Database, db: sqlite3.Database,
query: string query: string,
): Promise<unknown[]> { ): Promise<unknown[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
db.all(query, (err, rows) => { db.all(query, (err, rows) => {
@ -43,7 +43,7 @@ export function allDB<T>(
export function getDB<T>( export function getDB<T>(
db: sqlite3.Database, db: sqlite3.Database,
query: string, query: string,
params: any[] params: any[],
): Promise<T[]> { ): Promise<T[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
db.get(query, params, (err, row: any) => { db.get(query, params, (err, row: any) => {
@ -73,7 +73,7 @@ export function openDatabase() {
sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
(err: Error | null) => { (err: Error | null) => {
if (err) console.error(err.message); if (err) console.error(err.message);
} },
); );
} }
@ -105,7 +105,8 @@ export function createTables(db: sqlite3.Database) {
/* Créer la table registered_user dans la base de données */ /* Créer la table registered_user dans la base de données */
export function createRegisteredUserTable(db: sqlite3.Database): Promise<void> { export function createRegisteredUserTable(db: sqlite3.Database): Promise<void> {
const tableRegisteredUser = `CREATE TABLE IF NOT EXISTS registered_user (id_user INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT NOT NULL, password TEXT NOT NULL, permissions INTEGER NOT NULL, UNIQUE (login))`; const tableRegisteredUser =
`CREATE TABLE IF NOT EXISTS registered_user (id_user INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT NOT NULL, password TEXT NOT NULL, permissions INTEGER NOT NULL, UNIQUE (login))`;
return runDB(db, tableRegisteredUser, []); return runDB(db, tableRegisteredUser, []);
} }
@ -115,7 +116,7 @@ export function insertUser(
db: sqlite3.Database, db: sqlite3.Database,
login: string, login: string,
password: string, password: string,
permissions: number permissions: number,
) { ) {
const insertUserQuery = `INSERT INTO registered_user (login, password, permissions) VALUES (?, ?, ?)`; const insertUserQuery = `INSERT INTO registered_user (login, password, permissions) VALUES (?, ?, ?)`;
@ -126,7 +127,7 @@ export function insertUser(
export function updateUserLogin( export function updateUserLogin(
db: sqlite3.Database, db: sqlite3.Database,
id: number, id: number,
newLogin: string newLogin: string,
) { ) {
const updateUserLoginQuery = `UPDATE registered_user SET login = ? WHERE id_user = ?`; const updateUserLoginQuery = `UPDATE registered_user SET login = ? WHERE id_user = ?`;
@ -137,7 +138,7 @@ export function updateUserLogin(
export function updateUserPassword( export function updateUserPassword(
db: sqlite3.Database, db: sqlite3.Database,
id: number, id: number,
newPassword: string newPassword: string,
) { ) {
const updateUserPasswordQuery = `UPDATE registered_user SET password = ? WHERE id_user = ?`; const updateUserPasswordQuery = `UPDATE registered_user SET password = ? WHERE id_user = ?`;
@ -148,7 +149,7 @@ export function updateUserPassword(
export function updateUserPermissions( export function updateUserPermissions(
db: sqlite3.Database, db: sqlite3.Database,
id: number, id: number,
newPermissions: number newPermissions: number,
) { ) {
const updateUserPermissionsQuery = `UPDATE registered_user SET permissions = ? WHERE id_user = ?`; const updateUserPermissionsQuery = `UPDATE registered_user SET permissions = ? WHERE id_user = ?`;
@ -206,7 +207,8 @@ export function selectUserById(db: sqlite3.Database, id: number) {
/* Créer la table language dans la base de données */ /* Créer la table language dans la base de données */
export function createLanguageTable(db: sqlite3.Database): Promise<void> { export function createLanguageTable(db: sqlite3.Database): Promise<void> {
const tableLanguage = `CREATE TABLE IF NOT EXISTS language (id_language INTEGER PRIMARY KEY AUTOINCREMENT, designation TEXT NOT NULL, version INTEGER NOT NULL)`; const tableLanguage =
`CREATE TABLE IF NOT EXISTS language (id_language INTEGER PRIMARY KEY AUTOINCREMENT, designation TEXT NOT NULL, version INTEGER NOT NULL)`;
return runDB(db, tableLanguage, []); return runDB(db, tableLanguage, []);
} }
@ -214,7 +216,7 @@ export function createLanguageTable(db: sqlite3.Database): Promise<void> {
export function insertLanguage( export function insertLanguage(
db: sqlite3.Database, db: sqlite3.Database,
designation: string, designation: string,
version: number version: number,
) { ) {
const insertLanguageQuery = `INSERT INTO language (designation, version) VALUES (?, ?)`; const insertLanguageQuery = `INSERT INTO language (designation, version) VALUES (?, ?)`;
@ -225,7 +227,7 @@ export function insertLanguage(
export function updateLanguageDesignation( export function updateLanguageDesignation(
db: sqlite3.Database, db: sqlite3.Database,
id: number, id: number,
newDesignation: string newDesignation: string,
) { ) {
const updateLanguageDesignationQuery = `UPDATE language SET designation = ? WHERE id_language = ?`; const updateLanguageDesignationQuery = `UPDATE language SET designation = ? WHERE id_language = ?`;
@ -236,7 +238,7 @@ export function updateLanguageDesignation(
export function updateLanguageVersion( export function updateLanguageVersion(
db: sqlite3.Database, db: sqlite3.Database,
id: number, id: number,
newVersion: number newVersion: number,
) { ) {
const updateLanguageVersionQuery = `UPDATE language SET version = ? WHERE id_language = ?`; const updateLanguageVersionQuery = `UPDATE language SET version = ? WHERE id_language = ?`;
@ -282,7 +284,8 @@ export function selectLanguageById(db: sqlite3.Database, id: number) {
/* Créer la table work dans la base de données */ /* Créer la table work dans la base de données */
export function createWorkTable(db: sqlite3.Database): Promise<void> { export function createWorkTable(db: sqlite3.Database): Promise<void> {
const tableWork = `CREATE TABLE IF NOT EXISTS work (id_work INTEGER PRIMARY KEY AUTOINCREMENT, link CHAR(36) NOT NULL, user_id INTEGER REFERENCES registered_user(id_user), language_id INTEGER NOT NULL REFERENCES language(id_language), content TEXT NOT NULL)`; const tableWork =
`CREATE TABLE IF NOT EXISTS work (id_work INTEGER PRIMARY KEY AUTOINCREMENT, link CHAR(36) NOT NULL, user_id INTEGER REFERENCES registered_user(id_user), language_id INTEGER NOT NULL REFERENCES language(id_language), content TEXT NOT NULL)`;
return runDB(db, tableWork, []); return runDB(db, tableWork, []);
} }
@ -292,7 +295,7 @@ export function insertWork(
link: string, link: string,
user_id: number, user_id: number,
language_id: number, language_id: number,
content: string content: string,
) { ) {
const insertWorkQuery = `INSERT INTO work (link, user_id, language_id, content) VALUES (?, ?, ?, ?)`; const insertWorkQuery = `INSERT INTO work (link, user_id, language_id, content) VALUES (?, ?, ?, ?)`;

@ -19,11 +19,11 @@ export const IMAGES = {
export function allocateBuffer( export function allocateBuffer(
jobId: string, jobId: string,
code: string, code: string,
image: string image: string,
): Buffer { ): Buffer {
let cur = 0; let cur = 0;
const buffer = Buffer.allocUnsafe( const buffer = Buffer.allocUnsafe(
jobId.length + image.length + code.length + 9 jobId.length + image.length + code.length + 9,
); );
cur = buffer.writeUInt8(0, cur); cur = buffer.writeUInt8(0, cur);
cur += buffer.write(jobId, cur); cur += buffer.write(jobId, cur);

@ -1,12 +1,12 @@
import { rebaseUpdates, Update } from "@codemirror/collab";
import { ChangeSet, Text } from "@codemirror/state";
import cors from "@fastify/cors"; import cors from "@fastify/cors";
import websocket, { WebSocket } from '@fastify/websocket';
import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
import websocket, { WebSocket } from "@fastify/websocket";
import Fastify, { FastifyReply } from "fastify"; import Fastify, { FastifyReply } from "fastify";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { allocateBuffer, getRunner } from "runner"; import { allocateBuffer, getRunner } from "runner";
import { Pull, Push } from "zeromq"; import { Pull, Push } from "zeromq";
import { ChangeSet, Text } from "@codemirror/state";
import { Update, rebaseUpdates } from "@codemirror/collab";
import * as db from "./database"; import * as db from "./database";
const sender = new Push(); const sender = new Push();
@ -14,17 +14,25 @@ await sender.bind(`tcp://127.0.0.1:5557`);
const receiver = new Pull(); const receiver = new Pull();
await receiver.bind(`tcp://127.0.0.1:5558`); await receiver.bind(`tcp://127.0.0.1:5558`);
const clients: Record<string, FastifyReply> = {}; const clients: Record<string, FastifyReply> = {};
const generateId = () => nanoid(32); const generateId = () => nanoid(32);
let updates: Update[] = []; //let updates: Update[] = [];
let doc = Text.of(['']); //let doc = Text.of(["foo"]);
const liveClients: WebSocket[] = [];
type Room = {
sockets: WebSocket[];
updates: Update[];
doc: Text;
}
const rooms: Record<string, Room> = {};
function send(socket: WebSocket, requestId: number, payload: unknown) { function send(socket: WebSocket, requestId: number, payload: unknown) {
const response = { const response = {
_request: requestId, _request: requestId,
payload payload,
}; };
socket.send(JSON.stringify(response)); socket.send(JSON.stringify(response));
} }
@ -32,39 +40,79 @@ function send(socket: WebSocket, requestId: number, payload: unknown) {
const fastify = Fastify({ const fastify = Fastify({
logger: true, logger: true,
}).withTypeProvider<TypeBoxTypeProvider>(); }).withTypeProvider<TypeBoxTypeProvider>();
type Fastify = typeof fastify;
await fastify.register(cors, { await fastify.register(cors, {
origin: process.env.ALLOW_ORIGIN || '*', origin: process.env.ALLOW_ORIGIN || "*",
}); });
fastify.register(websocket); fastify.register(websocket);
fastify.get("/live", { websocket: true }, (socket, req) => {
liveClients.push(socket);
socket.on("message", message => { fastify.register(async function (fastify: Fastify) {
const data = JSON.parse(message.toString()); fastify.get(
const requestId = data._request; "/live/:roomId",
if (data.type === "pullUpdates") { {
send(socket, requestId, updates.slice(data.version)) schema: {
} else if (data.type === "pushUpdates") { params: Type.Object({
let received = data.updates.map((json: any) => ({ roomId: Type.String(),
clientID: json.clientID, })
changes: ChangeSet.fromJSON(json.changes) },
})) websocket: true
if (data.version != updates.length) { },
received = rebaseUpdates(received, updates.slice(data.version)) (socket, request) => {
} const { roomId } = request.params;
for (let update of received) { let room = rooms[roomId];
updates.push(update) if(!room){
doc = update.changes.apply(doc) room = {
sockets: [],
updates: [],
doc: Text.of([''])
};
rooms[roomId] = room;
} }
send(socket, requestId, received.map((update: any) => ({ room.sockets.push(socket);
clientID: update.clientID, socket.on("message", message => {
changes: update.changes.toJSON() const data = JSON.parse(message.toString());
}))); const requestId = data._request;
} else if (data.type == "getDocument") {
send(socket, requestId, {version: updates.length, doc: doc.toString()}) 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",
async (request, reply) => {
return generateId();
},
);
fastify.post("/run", { fastify.post("/run", {
schema: { schema: {
body: Type.Object({ body: Type.Object({
@ -122,7 +170,7 @@ fastify.post(
const { login, password, permissions } = request.body; const { login, password, permissions } = request.body;
db.insertUser(database, login, password, permissions); db.insertUser(database, login, password, permissions);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour mettre à jour le login d'un utilisateur */ /* Route pour mettre à jour le login d'un utilisateur */
@ -145,7 +193,7 @@ fastify.put(
const { newLogin } = request.body; const { newLogin } = request.body;
db.updateUserLogin(database, id, newLogin); db.updateUserLogin(database, id, newLogin);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour mettre à jour le mot de passe d'un utilisateur */ /* Route pour mettre à jour le mot de passe d'un utilisateur */
@ -168,7 +216,7 @@ fastify.put(
const { newPassword } = request.body; const { newPassword } = request.body;
db.updateUserPassword(database, id, newPassword); db.updateUserPassword(database, id, newPassword);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour mettre à jour les permissions d'un utilisateur */ /* Route pour mettre à jour les permissions d'un utilisateur */
@ -191,7 +239,7 @@ fastify.put(
const { newPermissions } = request.body; const { newPermissions } = request.body;
await db.updateUserPermissions(database, id, newPermissions); await db.updateUserPermissions(database, id, newPermissions);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour supprimer un utilisateur par son ID */ /* Route pour supprimer un utilisateur par son ID */
@ -210,7 +258,7 @@ fastify.delete(
const { id } = request.params; const { id } = request.params;
await db.deleteUserById(database, id); await db.deleteUserById(database, id);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour supprimer un utilisateur par son login */ /* Route pour supprimer un utilisateur par son login */
@ -227,7 +275,7 @@ fastify.delete(
const { login } = request.params; const { login } = request.params;
await db.deleteUserByLogin(database, login); await db.deleteUserByLogin(database, login);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour supprimer tous les utilisateurs */ /* Route pour supprimer tous les utilisateurs */
@ -258,7 +306,7 @@ fastify.get(
const { id } = request.params; const { id } = request.params;
const user = await db.selectUserById(database, id); const user = await db.selectUserById(database, id);
reply.send(user); reply.send(user);
} },
); );
/* Route pour récupérer un utilisateur par son login */ /* Route pour récupérer un utilisateur par son login */
@ -275,7 +323,7 @@ fastify.get(
const { login } = request.params; const { login } = request.params;
const user = await db.selectUserByLogin(database, login); const user = await db.selectUserByLogin(database, login);
reply.send(user); reply.send(user);
} },
); );
/* Route pour créer un language */ /* Route pour créer un language */
@ -293,7 +341,7 @@ fastify.post(
const { designation, version } = request.body; const { designation, version } = request.body;
db.insertLanguage(database, designation, version); db.insertLanguage(database, designation, version);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour mettre à jour la désignation d'un language */ /* Route pour mettre à jour la désignation d'un language */
@ -316,7 +364,7 @@ fastify.put(
const { newDesignation } = request.body; const { newDesignation } = request.body;
db.updateLanguageDesignation(database, id, newDesignation); db.updateLanguageDesignation(database, id, newDesignation);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour mettre à jour la version d'un language */ /* Route pour mettre à jour la version d'un language */
@ -339,7 +387,7 @@ fastify.put(
const { newVersion } = request.body; const { newVersion } = request.body;
db.updateLanguageVersion(database, id, newVersion); db.updateLanguageVersion(database, id, newVersion);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour supprimer un language */ /* Route pour supprimer un language */
@ -358,7 +406,7 @@ fastify.delete(
const { id } = request.params; const { id } = request.params;
db.deleteLanguage(database, id); db.deleteLanguage(database, id);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour supprimer tous les languages */ /* Route pour supprimer tous les languages */
@ -383,7 +431,7 @@ fastify.get(
const { id } = request.params; const { id } = request.params;
const language = await db.selectLanguageById(database, id); const language = await db.selectLanguageById(database, id);
reply.send(language); reply.send(language);
} },
); );
/* Route pour récupérer tous les languages */ /* Route pour récupérer tous les languages */
@ -409,7 +457,7 @@ fastify.post(
const { id_user, link, id_language, code } = request.body; const { id_user, link, id_language, code } = request.body;
db.insertWork(database, link, id_user, id_language, code); db.insertWork(database, link, id_user, id_language, code);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour récupérer tous les works */ /* Route pour récupérer tous les works */
@ -440,7 +488,7 @@ fastify.delete(
const { id } = request.params; const { id } = request.params;
db.deleteWork(database, id); db.deleteWork(database, id);
reply.send({ success: true }); reply.send({ success: true });
} },
); );
/* Route pour récupérer un work par son ID */ /* Route pour récupérer un work par son ID */
@ -459,7 +507,7 @@ fastify.get(
const { id } = request.params; const { id } = request.params;
const work = await db.selectWorkById(database, id); const work = await db.selectWorkById(database, id);
reply.send(work); 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 */ /* 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 */

Loading…
Cancel
Save