From cd6117e2657cc08ec132a6c84b214a132afeb6fc Mon Sep 17 00:00:00 2001 From: Glydric Date: Fri, 10 Nov 2023 10:59:26 +0100 Subject: [PATCH 01/12] added ping3 framework instead of command --- requirements.txt | 3 ++- src/handleSystemStream.py | 20 +++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index a2a1b23..19b12db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ opencv-python~=4.8.1.78 -numpy~=1.26.1 \ No newline at end of file +numpy~=1.26.1 +ping3~=4.0.4 \ No newline at end of file diff --git a/src/handleSystemStream.py b/src/handleSystemStream.py index db6696c..300dbd3 100644 --- a/src/handleSystemStream.py +++ b/src/handleSystemStream.py @@ -1,20 +1,20 @@ import os import subprocess -import cv2 +from ping3 import ping # Define the command to fetch the video stream using curl and extract frames using ffmpeg -command = "curl http://admin:@{}/livestream/11 --no-buffer -o - | ffmpeg -loglevel quiet -y -hide_banner -i - -vf 'fps=1' {}" +command = "curl http://admin:@{}/livestream/11 --no-buffer -o - | ffmpeg -loglevel quiet -y -hide_banner -i - -vf 'fps=1' {}" # TODO convert to array # The function uses outFormat where the char "%d" is the frame count, also remember to define all path def handle_status(ip: str, down: bool): if down: - print(f"Disconnected from ip: {ip}") + print(f"Failed connection with ip: {ip}") else: print(f"Connected with ip: {ip}") - pass # TODO save log + # TODO save log def handle_connection(ip: str, out_format: str): @@ -36,17 +36,11 @@ def obtain_frames(ip: str, out_format: str, debug: bool): raise Exception("Path not usable") while True: - try: - subprocess.run( - "ping -c 1 {}".format(ip), - stdout=subprocess.PIPE, - shell=True, - check=True - ) + if ping(ip) is None: + handle_status(ip, True) + else: handle_status(ip, False) handle_connection(ip, out_format) - except subprocess.CalledProcessError: - handle_status(ip, True) def dir_check(directory: str, force: bool): From 2feb887be70d01218d29e3cc2e90478e9af73b8b Mon Sep 17 00:00:00 2001 From: Glydric Date: Fri, 1 Dec 2023 12:50:18 +0100 Subject: [PATCH 02/12] FINALLY implemented ffmpeg to opencv --- ImageRecognition/src/handleSystemStream.py | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/ImageRecognition/src/handleSystemStream.py b/ImageRecognition/src/handleSystemStream.py index 300dbd3..a1595db 100644 --- a/ImageRecognition/src/handleSystemStream.py +++ b/ImageRecognition/src/handleSystemStream.py @@ -1,12 +1,11 @@ import os import subprocess +import cv2 +import ffmpeg +import numpy as np from ping3 import ping -# Define the command to fetch the video stream using curl and extract frames using ffmpeg -command = "curl http://admin:@{}/livestream/11 --no-buffer -o - | ffmpeg -loglevel quiet -y -hide_banner -i - -vf 'fps=1' {}" # TODO convert to array - - # The function uses outFormat where the char "%d" is the frame count, also remember to define all path def handle_status(ip: str, down: bool): if down: @@ -17,14 +16,26 @@ def handle_status(ip: str, down: bool): # TODO save log +opts = {"loglevel": "quiet", "r": "30", "f": "avfoundation"} + + def handle_connection(ip: str, out_format: str): # Run the command using subprocess + cmd = (ffmpeg + .input("0:", **opts) + .output("pipe:", format="rawvideo", pix_fmt="bgr24") + .run_async(pipe_stdout=True) + ) + while True: + raw_frame = cmd.stdout.read(1920 * 1080 * 3) + if not raw_frame: + break + frame = np.frombuffer(raw_frame, np.uint8).reshape((1080, 1920, 3)) + + cv2.imshow("VideoFrame", frame) + if cv2.waitKey(1) & 0xFF == ord("q"): + break try: - subprocess.run( - command.format(ip, out_format), - shell=True, - check=True - ) print("Frames extracted successfully.") except subprocess.CalledProcessError as e: print(e.returncode) From 3ed3fc19f4aeaff708983eb7026a7bacd8e8ed03 Mon Sep 17 00:00:00 2001 From: Glydric Date: Fri, 1 Dec 2023 14:29:00 +0100 Subject: [PATCH 03/12] improvements --- ImageRecognition/main.py | 5 +- ImageRecognition/requirements.txt | 3 +- ImageRecognition/src/handleSystemStream.py | 60 +++++++--------------- 3 files changed, 22 insertions(+), 46 deletions(-) diff --git a/ImageRecognition/main.py b/ImageRecognition/main.py index e77bd57..770a4a7 100644 --- a/ImageRecognition/main.py +++ b/ImageRecognition/main.py @@ -1,6 +1,6 @@ import concurrent.futures -from src.handleSystemStream import obtain_frames +from src.handleSystemStream import obtain_frames, handle_connection IPs = [ "172.20.14.97", @@ -15,11 +15,9 @@ def start(debug: bool): - path = "../../img/" with concurrent.futures.ThreadPoolExecutor() as executor: args: (str, str, bool) = [( ip, - f'{path}out/stream/{ip}/%d.jpg', debug ) for ip in IPs] @@ -28,4 +26,5 @@ def start(debug: bool): if __name__ == "__main__": + # handle_connection("172.20.14.97") start(False) diff --git a/ImageRecognition/requirements.txt b/ImageRecognition/requirements.txt index 19b12db..248c2ec 100644 --- a/ImageRecognition/requirements.txt +++ b/ImageRecognition/requirements.txt @@ -1,3 +1,4 @@ opencv-python~=4.8.1.78 numpy~=1.26.1 -ping3~=4.0.4 \ No newline at end of file +ping3~=4.0.4 +ffmpeg-python~=0.2.0 \ No newline at end of file diff --git a/ImageRecognition/src/handleSystemStream.py b/ImageRecognition/src/handleSystemStream.py index a1595db..2a4a66f 100644 --- a/ImageRecognition/src/handleSystemStream.py +++ b/ImageRecognition/src/handleSystemStream.py @@ -1,14 +1,11 @@ -import os -import subprocess - import cv2 import ffmpeg import numpy as np from ping3 import ping -# The function uses outFormat where the char "%d" is the frame count, also remember to define all path -def handle_status(ip: str, down: bool): - if down: + +def handle_status(ip: str, is_available: bool): + if is_available: print(f"Failed connection with ip: {ip}") else: print(f"Connected with ip: {ip}") @@ -16,13 +13,16 @@ def handle_status(ip: str, down: bool): # TODO save log -opts = {"loglevel": "quiet", "r": "30", "f": "avfoundation"} +opts = { + "loglevel": "quiet", # "r": "30", "f": "avfoundation" + "headers": 'Authorization: Basic YWRtaW46' +} -def handle_connection(ip: str, out_format: str): +def handle_connection(ip: str, debug: bool): # Run the command using subprocess cmd = (ffmpeg - .input("0:", **opts) + .input(f"http://{ip}/livestream/11", **opts) .output("pipe:", format="rawvideo", pix_fmt="bgr24") .run_async(pipe_stdout=True) ) @@ -32,40 +32,16 @@ def handle_connection(ip: str, out_format: str): break frame = np.frombuffer(raw_frame, np.uint8).reshape((1080, 1920, 3)) - cv2.imshow("VideoFrame", frame) - if cv2.waitKey(1) & 0xFF == ord("q"): - break - try: - print("Frames extracted successfully.") - except subprocess.CalledProcessError as e: - print(e.returncode) - print(f"Error: {e}") - + if debug: + cv2.imshow(f"VideoFrame{ip}", frame) + if cv2.waitKey(1) & 0xFF == ord("q"): + break -def obtain_frames(ip: str, out_format: str, debug: bool): - if not dir_check(out_format, not debug): - raise Exception("Path not usable") +def obtain_frames(ip: str, debug: bool): while True: - if ping(ip) is None: - handle_status(ip, True) - else: - handle_status(ip, False) - handle_connection(ip, out_format) - - -def dir_check(directory: str, force: bool): - out_path = os.path.dirname(directory) - - if not os.path.exists(out_path): - if force: - os.makedirs(out_path) - return True - - choice = input(f"Directory '{out_path}' not exists. You want to create it? (Yes/No): ") - if choice.lower() == 'no': - print("Operazione annullata.") - return False + available = ping(ip) is not None + handle_status(ip, available) - os.makedirs(out_path) - return True + if available: + handle_connection(ip, debug) From 7c1c00bd5db9c163731e71a52014e4c8cd143a56 Mon Sep 17 00:00:00 2001 From: Glydric Date: Mon, 4 Dec 2023 14:37:00 +0100 Subject: [PATCH 04/12] telegram disable notification and prettier --- Backend/.eslintrc.js | 23 +++++----- Backend/src/DataType.ts | 2 +- Backend/src/app.module.ts | 14 +++--- .../app/frontend/frontend.controller.spec.ts | 8 ++-- Backend/src/app/login.controller.ts | 22 +++++++--- .../machineLearning.controller.spec.ts | 11 +++-- .../machineLearning.controller.ts | 23 +++++----- Backend/src/database/database.service.spec.ts | 6 +-- Backend/src/database/database.service.ts | 36 ++++++++++----- Backend/src/main.ts | 16 ++++--- Backend/src/telegram/telegram.service.ts | 44 ++++++++++++++++--- Backend/src/user.dto.ts | 5 +-- .../src/validators/camera-id/camera.pipe.ts | 8 +++- Backend/test/app.e2e-spec.ts | 10 ++--- 14 files changed, 144 insertions(+), 84 deletions(-) diff --git a/Backend/.eslintrc.js b/Backend/.eslintrc.js index ee1362c..259de13 100644 --- a/Backend/.eslintrc.js +++ b/Backend/.eslintrc.js @@ -1,22 +1,25 @@ module.exports = { - parser: "@typescript-eslint/parser", - extends: ["plugin:@typescript-eslint/recommended"], + parser: '@typescript-eslint/parser', parserOptions: { - project: "tsconfig.json", + project: 'tsconfig.json', tsconfigRootDir: __dirname, - sourceType: "module", + sourceType: 'module', }, - plugins: ["@typescript-eslint/eslint-plugin"], + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], root: true, env: { node: true, jest: true, }, - ignorePatterns: [".eslintrc.js"], + ignorePatterns: ['.eslintrc.js'], rules: { - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-explicit-any": "off", + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', }, }; diff --git a/Backend/src/DataType.ts b/Backend/src/DataType.ts index 4cd26ba..31ca56f 100644 --- a/Backend/src/DataType.ts +++ b/Backend/src/DataType.ts @@ -2,7 +2,7 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Binary } from 'mongodb'; +import { Binary } from "mongodb"; export default class DataType { cameraId: ID; diff --git a/Backend/src/app.module.ts b/Backend/src/app.module.ts index 6d85d95..ee1bd58 100644 --- a/Backend/src/app.module.ts +++ b/Backend/src/app.module.ts @@ -2,13 +2,13 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Module } from '@nestjs/common'; -import { MachineLearningController } from './app/machineLearning/machineLearning.controller'; -import { FrontendController } from './app/frontend/frontend.controller'; -import { DatabaseService } from './database/database.service'; -import { TelegramService } from './telegram/telegram.service'; -import { JwtModule } from '@nestjs/jwt'; -import { LoginController } from './app/login.controller'; +import { Module } from "@nestjs/common"; +import { MachineLearningController } from "./app/machineLearning/machineLearning.controller"; +import { FrontendController } from "./app/frontend/frontend.controller"; +import { DatabaseService } from "./database/database.service"; +import { TelegramService } from "./telegram/telegram.service"; +import { JwtModule } from "@nestjs/jwt"; +import { LoginController } from "./app/login.controller"; @Module({ imports: [ diff --git a/Backend/src/app/frontend/frontend.controller.spec.ts b/Backend/src/app/frontend/frontend.controller.spec.ts index 36f01df..5c1a0b2 100644 --- a/Backend/src/app/frontend/frontend.controller.spec.ts +++ b/Backend/src/app/frontend/frontend.controller.spec.ts @@ -2,10 +2,10 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from '@nestjs/testing'; -import { FrontendController } from './frontend.controller'; -import { DatabaseService } from '../../database/database.service'; -import { JwtModule } from '@nestjs/jwt'; +import { Test, TestingModule } from "@nestjs/testing"; +import { FrontendController } from "./frontend.controller"; +import { DatabaseService } from "../../database/database.service"; +import { JwtModule } from "@nestjs/jwt"; describe("FrontendController", () => { let controller: FrontendController; diff --git a/Backend/src/app/login.controller.ts b/Backend/src/app/login.controller.ts index 86d10f6..03640db 100644 --- a/Backend/src/app/login.controller.ts +++ b/Backend/src/app/login.controller.ts @@ -2,12 +2,17 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Body, Controller, Header, Post } from '@nestjs/common'; -import { ApiBody, ApiCreatedResponse, ApiNotFoundResponse, ApiTags } from '@nestjs/swagger'; -import UserDTO from '../user.dto'; -import { DatabaseService } from '../database/database.service'; -import { JwtService } from '@nestjs/jwt'; -import * as process from 'process'; +import { Body, Controller, Header, Post } from "@nestjs/common"; +import { + ApiBody, + ApiCreatedResponse, + ApiNotFoundResponse, + ApiTags, +} from "@nestjs/swagger"; +import UserDTO from "../user.dto"; +import { DatabaseService } from "../database/database.service"; +import { JwtService } from "@nestjs/jwt"; +import * as process from "process"; @ApiTags("Frontend") @Controller("/") @@ -23,7 +28,10 @@ export class LoginController { examples: { a: { summary: "Existing user", - value: { name: process.env.CSD_USER, password: process.env.CSD_PASSWORD }, + value: { + name: process.env.CSD_USER, + password: process.env.CSD_PASSWORD, + }, }, b: { summary: "Non existing user", diff --git a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts index 0f0742e..a6c7e48 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts @@ -2,11 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from '@nestjs/testing'; -import { MachineLearningController } from './machineLearning.controller'; -import { DatabaseService } from '../../database/database.service'; -import { TelegramService } from '../../telegram/telegram.service'; -import { JwtModule } from '@nestjs/jwt'; +import { Test, TestingModule } from "@nestjs/testing"; +import { MachineLearningController } from "./machineLearning.controller"; +import { DatabaseService } from "../../database/database.service"; +import { TelegramService } from "../../telegram/telegram.service"; +import { JwtModule } from "@nestjs/jwt"; describe("MachineLearningController", () => { let controller: MachineLearningController; @@ -24,7 +24,6 @@ describe("MachineLearningController", () => { }).compile(); controller = app.get(MachineLearningController); - }); it("should exists", () => { diff --git a/Backend/src/app/machineLearning/machineLearning.controller.ts b/Backend/src/app/machineLearning/machineLearning.controller.ts index 09a840c..a24e4c3 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.ts @@ -12,10 +12,10 @@ import { UploadedFile, UseGuards, UseInterceptors, -} from '@nestjs/common'; -import { DatabaseService } from '../../database/database.service'; -import { FileInterceptor } from '@nestjs/platform-express'; -import DataType from '../../DataType'; +} from "@nestjs/common"; +import { DatabaseService } from "../../database/database.service"; +import { FileInterceptor } from "@nestjs/platform-express"; +import DataType from "../../DataType"; import { ApiBadRequestResponse, ApiBearerAuth, @@ -26,10 +26,13 @@ import { ApiParam, ApiProperty, ApiTags, -} from '@nestjs/swagger'; -import { CameraIds, CameraValidator } from '../../validators/camera-id/camera.pipe'; -import { AuthGuard } from '../../auth/auth.guard'; -import { TelegramService } from '../../telegram/telegram.service'; +} from "@nestjs/swagger"; +import { + CameraIds, + CameraValidator, +} from "../../validators/camera-id/camera.pipe"; +import { AuthGuard } from "../../auth/auth.guard"; +import { TelegramService } from "../../telegram/telegram.service"; class ImageUploadDto { @ApiProperty({ @@ -123,9 +126,7 @@ export class MachineLearningController { ) { const date = new Date(); - await this.database.addData( - new DataType(cameraId, date, null, file), - ); + await this.database.addData(new DataType(cameraId, date, null, file)); await this.telegramApi.sendIntrusionDetectionNotification( cameraId, date, diff --git a/Backend/src/database/database.service.spec.ts b/Backend/src/database/database.service.spec.ts index 309688b..271a3f6 100644 --- a/Backend/src/database/database.service.spec.ts +++ b/Backend/src/database/database.service.spec.ts @@ -2,8 +2,8 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from '@nestjs/testing'; -import { DatabaseService } from './database.service'; +import { Test, TestingModule } from "@nestjs/testing"; +import { DatabaseService } from "./database.service"; describe("DatabaseService", () => { let databaseService: DatabaseService; @@ -42,7 +42,7 @@ describe("DatabaseService", () => { ).not.toThrow(), ); - const spy = jest.fn() + const spy = jest.fn(); await databaseService.getImage(1, "this will make it throw").catch(spy); expect(spy).toHaveBeenCalled(); }); diff --git a/Backend/src/database/database.service.ts b/Backend/src/database/database.service.ts index bfc9ac9..e3b6501 100644 --- a/Backend/src/database/database.service.ts +++ b/Backend/src/database/database.service.ts @@ -2,14 +2,18 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Injectable, NotAcceptableException, NotFoundException } from '@nestjs/common'; -import { Db, Document, Filter, MongoClient } from 'mongodb'; -import 'dotenv/config'; -import DataType from '../DataType'; -import { CameraIds } from '../validators/camera-id/camera.pipe'; -import { FiltersAvailable } from '../validators/filters/filters.pipe'; -import * as process from 'process'; -import UserDTO from '../user.dto'; +import { + Injectable, + NotAcceptableException, + NotFoundException, +} from "@nestjs/common"; +import { Db, Document, Filter, MatchKeysAndValues, MongoClient } from "mongodb"; +import "dotenv/config"; +import DataType from "../DataType"; +import { CameraIds } from "../validators/camera-id/camera.pipe"; +import { FiltersAvailable } from "../validators/filters/filters.pipe"; +import * as process from "process"; +import UserDTO from "../user.dto"; const url = `mongodb://${process.env.MONGO_INITDB_ROOT_USERNAME}:${process.env.MONGO_INITDB_ROOT_PASSWORD}@${process.env.MONGO_HOST}`; @@ -98,12 +102,20 @@ export class DatabaseService { return array; } + async checkAndUpdateUser( + user: Filter, + newData: MatchKeysAndValues, + ) { + await this.getRawDataArray("users", user, "User Not found"); + + return this.DB.collection("users").updateOne(user, { + $set: newData, + }); + } + // This will also check if the user exists async checkUserAndUpdateTelegramId(telegramId: number, userData: UserDTO) { - await this.getRawDataArray("users", userData, "User Not found"); - return await this.DB.collection("users").updateOne(userData, { - $set: { telegramId: telegramId }, - }); + return await this.checkAndUpdateUser(userData, { telegramId: telegramId }); } private getFilter(filter?: FiltersAvailable) { diff --git a/Backend/src/main.ts b/Backend/src/main.ts index 91c4d06..4f5c2e3 100644 --- a/Backend/src/main.ts +++ b/Backend/src/main.ts @@ -2,14 +2,14 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; -import { ValidationPipe } from '@nestjs/common'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { NestFactory } from "@nestjs/core"; +import { AppModule } from "./app.module"; +import { ValidationPipe } from "@nestjs/common"; +import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useGlobalPipes(new ValidationPipe()) + app.useGlobalPipes(new ValidationPipe()); const config = new DocumentBuilder() .setTitle("Complex System Design") @@ -29,10 +29,12 @@ async function bootstrap() { ) .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('swagger-api', app, document); + SwaggerModule.setup("swagger-api", app, document); await app.listen(8080); - console.log("\nApp started, look at http://localhost:8080/swagger-api for the documentation") + console.log( + "\nApp started, look at http://localhost:8080/swagger-api for the documentation", + ); } bootstrap(); diff --git a/Backend/src/telegram/telegram.service.ts b/Backend/src/telegram/telegram.service.ts index 47a748c..3c1d24b 100644 --- a/Backend/src/telegram/telegram.service.ts +++ b/Backend/src/telegram/telegram.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, NotFoundException } from "@nestjs/common"; import * as TelegramBot from "node-telegram-bot-api"; import * as process from "process"; import { DatabaseService } from "../database/database.service"; @@ -13,6 +13,8 @@ export class TelegramService { this.bot.onText(/\/user (.+)/, (m) => this.onLogin(m)); this.bot.onText(/\/start/, (m) => this.welcome(m)); + this.bot.onText(/\/disable/, (m) => this.setIntrusionDetection(m, false)); + this.bot.onText(/\/enable/, (m) => this.setIntrusionDetection(m, true)); } private async welcome(msg: TelegramBot.Message) { @@ -20,7 +22,9 @@ export class TelegramService { msg.chat.id, "Welcome to CSS bot\n" + "This bot was developed by Leonardo Migliorelli\n" + - "Please login with '/user '", + "Please login with '/user '\n" + + "To disable intrusion detection, type '/disable'" + + "To reenable intrusion detection, type '/enable'", ); } @@ -33,10 +37,14 @@ export class TelegramService { }; try { - await this.databaseService.checkUserAndUpdateTelegramId( - msg.chat.id, - userData, - ); + await this.databaseService.checkAndUpdateUser(userData, { + telegramId: msg.chat.id, + }); + + // await this.databaseService.checkUserAndUpdateTelegramId( + // msg.chat.id, + // userData, + // ); } catch (e) { await this.bot.sendMessage(msg.chat.id, e.message); return; @@ -50,6 +58,29 @@ export class TelegramService { ); } + private async setIntrusionDetection( + msg: TelegramBot.Message, + status: boolean, + ) { + try { + await this.databaseService.checkAndUpdateUser( + { telegramId: msg.chat.id }, + { getsAlerts: status }, + ); + } catch (e) { + await this.bot.sendMessage( + msg.chat.id, + e == NotFoundException ? "You are not logged in" : e.message, + ); + return; + } + await this.bot.sendMessage( + msg.chat.id, + `Intrusion detection ${status ? "enabled" : "disabled"}`, + ); + return; + } + async sendIntrusionDetectionNotification( cameraId: number, date: Date, @@ -58,6 +89,7 @@ export class TelegramService { const users = await this.databaseService.getRawDataArray("users"); users + .filter((user) => user.getsAlerts) .map((user) => user.telegramId) .forEach((id) => { this.bot.sendPhoto(id, image, { diff --git a/Backend/src/user.dto.ts b/Backend/src/user.dto.ts index 09d7bd8..a3243b0 100644 --- a/Backend/src/user.dto.ts +++ b/Backend/src/user.dto.ts @@ -2,12 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ - -import { IsString } from 'class-validator'; +import { IsString } from "class-validator"; export default class UserDTO { @IsString() readonly name: string; @IsString() readonly password: string; -} \ No newline at end of file +} diff --git a/Backend/src/validators/camera-id/camera.pipe.ts b/Backend/src/validators/camera-id/camera.pipe.ts index 64ba920..e561411 100644 --- a/Backend/src/validators/camera-id/camera.pipe.ts +++ b/Backend/src/validators/camera-id/camera.pipe.ts @@ -1,4 +1,9 @@ -import { HttpException, HttpStatus, Injectable, PipeTransform } from '@nestjs/common'; +import { + HttpException, + HttpStatus, + Injectable, + PipeTransform, +} from "@nestjs/common"; @Injectable() export class CameraValidator implements PipeTransform { @@ -18,4 +23,3 @@ export class CameraValidator implements PipeTransform { export const cameraIds = [1, 2, 3, 4, 5, 6, 7, 8, 9] as const; export type CameraIds = (typeof cameraIds)[number]; - diff --git a/Backend/test/app.e2e-spec.ts b/Backend/test/app.e2e-spec.ts index 7db7b53..ffddb8c 100644 --- a/Backend/test/app.e2e-spec.ts +++ b/Backend/test/app.e2e-spec.ts @@ -2,11 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import { AppModule } from '../src/app.module'; +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication } from "@nestjs/common"; +import { AppModule } from "../src/app.module"; -describe('MachinelearningController (e2e)', () => { +describe("MachinelearningController (e2e)", () => { let app: INestApplication; beforeEach(async () => { @@ -18,7 +18,7 @@ describe('MachinelearningController (e2e)', () => { await app.init(); }); - it('/ (GET)', () => { + it("/ (GET)", () => { // return request(app.getHttpServer()) // .get('/') // .expect(200) From a0f9cefe101651ff1afb70324d05a0d4998b90f4 Mon Sep 17 00:00:00 2001 From: Glydric Date: Mon, 4 Dec 2023 15:02:14 +0100 Subject: [PATCH 05/12] linter/prettier --- Backend/.eslintrc.js | 25 --------------------- Backend/.eslintrc.json | 28 ++++++++++++++++++++++++ Backend/src/telegram/telegram.service.ts | 4 ++++ 3 files changed, 32 insertions(+), 25 deletions(-) delete mode 100644 Backend/.eslintrc.js create mode 100644 Backend/.eslintrc.json diff --git a/Backend/.eslintrc.js b/Backend/.eslintrc.js deleted file mode 100644 index 259de13..0000000 --- a/Backend/.eslintrc.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, -}; diff --git a/Backend/.eslintrc.json b/Backend/.eslintrc.json new file mode 100644 index 0000000..f959725 --- /dev/null +++ b/Backend/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint/eslint-plugin" + ], + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "root": true, + "env": { + "node": true, + "jest": true + }, + "ignorePatterns": [ + ".eslintrc.js" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off" + } +} diff --git a/Backend/src/telegram/telegram.service.ts b/Backend/src/telegram/telegram.service.ts index 3c1d24b..4f4be9a 100644 --- a/Backend/src/telegram/telegram.service.ts +++ b/Backend/src/telegram/telegram.service.ts @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2023. Leonardo Migliorelli + */ + import { Injectable, NotFoundException } from "@nestjs/common"; import * as TelegramBot from "node-telegram-bot-api"; import * as process from "process"; From 2be9faa0187aff03383f18aa039a902651247975 Mon Sep 17 00:00:00 2001 From: Glydric Date: Tue, 5 Dec 2023 19:07:07 +0100 Subject: [PATCH 06/12] prettier fix and websocket implementation --- Backend/.eslintrc.json | 2 +- Backend/.prettierrc | 4 + Backend/package-lock.json | 331 +++++++++++++++++- Backend/package.json | 9 +- Backend/src/DataType.ts | 2 +- Backend/src/app.module.ts | 16 +- .../app/frontend/frontend.controller.spec.ts | 12 +- .../src/app/frontend/frontend.controller.ts | 62 ++-- Backend/src/app/login.controller.ts | 30 +- .../machineLearning.controller.spec.ts | 14 +- .../machineLearning.controller.ts | 68 ++-- Backend/src/auth/auth.guard.spec.ts | 14 +- Backend/src/auth/auth.guard.ts | 36 +- .../cameraStream/cameraStream.gateway.spec.ts | 18 + .../src/cameraStream/cameraStream.module.ts | 16 + .../src/cameraStream/cameraStreamGateway.ts | 71 ++++ Backend/src/database/database.service.spec.ts | 20 +- Backend/src/database/database.service.ts | 46 +-- Backend/src/env.spec.ts | 16 +- Backend/src/main.ts | 38 +- Backend/src/telegram/telegram.service.ts | 22 +- Backend/src/user.dto.ts | 2 +- .../validators/camera-id/camera.pipe.spec.ts | 10 +- .../src/validators/camera-id/camera.pipe.ts | 4 +- .../validators/filters/filters.pipe.spec.ts | 10 +- .../src/validators/filters/filters.pipe.ts | 10 +- Backend/test/app.e2e-spec.ts | 10 +- 27 files changed, 674 insertions(+), 219 deletions(-) create mode 100644 Backend/.prettierrc create mode 100644 Backend/src/cameraStream/cameraStream.gateway.spec.ts create mode 100644 Backend/src/cameraStream/cameraStream.module.ts create mode 100644 Backend/src/cameraStream/cameraStreamGateway.ts diff --git a/Backend/.eslintrc.json b/Backend/.eslintrc.json index f959725..1e11aff 100644 --- a/Backend/.eslintrc.json +++ b/Backend/.eslintrc.json @@ -20,8 +20,8 @@ ".eslintrc.js" ], "rules": { - "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off" } diff --git a/Backend/.prettierrc b/Backend/.prettierrc new file mode 100644 index 0000000..dcb7279 --- /dev/null +++ b/Backend/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/Backend/package-lock.json b/Backend/package-lock.json index 4d8786c..0efaeac 100644 --- a/Backend/package-lock.json +++ b/Backend/package-lock.json @@ -13,14 +13,19 @@ "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.2.10", + "@nestjs/platform-ws": "^10.2.10", "@nestjs/swagger": "^7.1.16", + "@nestjs/websockets": "^10.2.10", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.3.1", "mongodb": "^6.3.0", "node-telegram-bot-api": "^0.64.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "simple-peer": "^9.11.1", + "ws": "^8.14.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -33,8 +38,10 @@ "@types/multer": "^1.4.10", "@types/node": "^20.3.1", "@types/node-telegram-bot-api": "^0.63.3", + "@types/simple-peer": "^9.11.8", "@types/source-map-support": "^0.5.10", "@types/supertest": "^2.0.12", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", @@ -1788,6 +1795,42 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.10.tgz", + "integrity": "sha512-JBuemeIBp2mpp+z7D12oa22k83TnDTxyQDMKZpO/B2/QnBVR+2C4EZ07/XOct14LQXO6vIjmT0iYYCZbNvczjw==", + "dependencies": { + "socket.io": "4.7.2", + "tslib": "2.6.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-10.2.10.tgz", + "integrity": "sha512-x9L7jixAEtbNjP9hIm9Fmx+kL9ruFQLu2cUb0EdSNtwK/efAJZ3+Taz9T8g/Nm5DG4k0356X6hmRk74ChJHg9g==", + "dependencies": { + "tslib": "2.6.2", + "ws": "8.14.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, "node_modules/@nestjs/schematics": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.3.tgz", @@ -1862,6 +1905,28 @@ } } }, + "node_modules/@nestjs/websockets": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.10.tgz", + "integrity": "sha512-L1AkxwLUj/ntk26jO1SXYl3GRElQE6Fikzfy/3MPFURk0GDs7tHUzLcb8BC8q8u5ZpUjBAC2wFVQzrY5R0MHNw==", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.6.2" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1968,6 +2033,11 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -2058,12 +2128,25 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, "node_modules/@types/cookiejar": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-LZ8SD3LpNmLMDLkG2oCBjZg+ETnx6XdCjydUE0HwojDmnDfDUnhMKKbtth1TZh+hzcqb03azrYWoXLS8sMXdqg==", "dev": true }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.7", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", @@ -2281,6 +2364,15 @@ "@types/node": "*" } }, + "node_modules/@types/simple-peer": { + "version": "9.11.8", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.8.tgz", + "integrity": "sha512-rvqefdp2rvIA6wiomMgKWd2UZNPe6LM2EV5AuY3CPQJF+8TbdrL5TjYdMf0VAjGczzlkH4l1NjDkihwbj3Xodw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-map-support": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", @@ -2348,6 +2440,15 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.29", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", @@ -3128,7 +3229,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -3144,6 +3244,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3887,7 +3995,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4297,6 +4404,62 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -4310,6 +4473,11 @@ "node": ">=10.13.0" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5274,6 +5442,11 @@ "node": ">=6.9.0" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5672,7 +5845,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7661,6 +7833,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -8219,7 +8399,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -8239,7 +8418,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -9016,6 +9194,70 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/simple-peer/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -9031,6 +9273,63 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -10412,6 +10711,26 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/Backend/package.json b/Backend/package.json index 9ad6c5e..8fd2285 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -24,14 +24,19 @@ "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.2.10", + "@nestjs/platform-ws": "^10.2.10", "@nestjs/swagger": "^7.1.16", + "@nestjs/websockets": "^10.2.10", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.3.1", "mongodb": "^6.3.0", "node-telegram-bot-api": "^0.64.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "simple-peer": "^9.11.1", + "ws": "^8.14.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -44,8 +49,10 @@ "@types/multer": "^1.4.10", "@types/node": "^20.3.1", "@types/node-telegram-bot-api": "^0.63.3", + "@types/simple-peer": "^9.11.8", "@types/source-map-support": "^0.5.10", "@types/supertest": "^2.0.12", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", diff --git a/Backend/src/DataType.ts b/Backend/src/DataType.ts index 31ca56f..4cd26ba 100644 --- a/Backend/src/DataType.ts +++ b/Backend/src/DataType.ts @@ -2,7 +2,7 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Binary } from "mongodb"; +import { Binary } from 'mongodb'; export default class DataType { cameraId: ID; diff --git a/Backend/src/app.module.ts b/Backend/src/app.module.ts index ee1bd58..92466c3 100644 --- a/Backend/src/app.module.ts +++ b/Backend/src/app.module.ts @@ -2,13 +2,14 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Module } from "@nestjs/common"; -import { MachineLearningController } from "./app/machineLearning/machineLearning.controller"; -import { FrontendController } from "./app/frontend/frontend.controller"; -import { DatabaseService } from "./database/database.service"; -import { TelegramService } from "./telegram/telegram.service"; -import { JwtModule } from "@nestjs/jwt"; -import { LoginController } from "./app/login.controller"; +import { Module } from '@nestjs/common'; +import { MachineLearningController } from './app/machineLearning/machineLearning.controller'; +import { FrontendController } from './app/frontend/frontend.controller'; +import { DatabaseService } from './database/database.service'; +import { TelegramService } from './telegram/telegram.service'; +import { JwtModule } from '@nestjs/jwt'; +import { LoginController } from './app/login.controller'; +import { CameraStreamModule } from './cameraStream/cameraStream.module'; @Module({ imports: [ @@ -18,6 +19,7 @@ import { LoginController } from "./app/login.controller"; // FIXME Error: Payload as string is not allowed with the following sign options: expiresIn // signOptions: { expiresIn: "60s" }, }), + CameraStreamModule, ], controllers: [MachineLearningController, FrontendController, LoginController], providers: [DatabaseService, TelegramService], diff --git a/Backend/src/app/frontend/frontend.controller.spec.ts b/Backend/src/app/frontend/frontend.controller.spec.ts index 5c1a0b2..1bc4d4b 100644 --- a/Backend/src/app/frontend/frontend.controller.spec.ts +++ b/Backend/src/app/frontend/frontend.controller.spec.ts @@ -2,12 +2,12 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { FrontendController } from "./frontend.controller"; -import { DatabaseService } from "../../database/database.service"; -import { JwtModule } from "@nestjs/jwt"; +import { Test, TestingModule } from '@nestjs/testing'; +import { FrontendController } from './frontend.controller'; +import { DatabaseService } from '../../database/database.service'; +import { JwtModule } from '@nestjs/jwt'; -describe("FrontendController", () => { +describe('FrontendController', () => { let controller: FrontendController; beforeEach(async () => { @@ -25,7 +25,7 @@ describe("FrontendController", () => { controller = module.get(FrontendController); }); - it("should be defined", () => { + it('should be defined', () => { expect(controller).toBeDefined(); }); }); diff --git a/Backend/src/app/frontend/frontend.controller.ts b/Backend/src/app/frontend/frontend.controller.ts index d051f86..863a0c1 100644 --- a/Backend/src/app/frontend/frontend.controller.ts +++ b/Backend/src/app/frontend/frontend.controller.ts @@ -5,8 +5,8 @@ import { Param, StreamableFile, UseGuards, -} from "@nestjs/common"; -import { DatabaseService } from "../../database/database.service"; +} from '@nestjs/common'; +import { DatabaseService } from '../../database/database.service'; import { ApiBadRequestResponse, ApiBearerAuth, @@ -14,45 +14,45 @@ import { ApiParam, ApiTags, ApiUnauthorizedResponse, -} from "@nestjs/swagger"; +} from '@nestjs/swagger'; import { filters, FiltersAvailable, FiltersValidator, -} from "../../validators/filters/filters.pipe"; +} from '../../validators/filters/filters.pipe'; import { CameraIds, CameraValidator, -} from "../../validators/camera-id/camera.pipe"; -import { AuthGuard } from "../../auth/auth.guard"; +} from '../../validators/camera-id/camera.pipe'; +import { AuthGuard } from '../../auth/auth.guard'; const filterParams = { - name: "filter", - type: "string", + name: 'filter', + type: 'string', examples: { online: { - value: "online", + value: 'online', }, offline: { - value: "offline", + value: 'offline', }, intrusionDetection: { - value: "intrusionDetection", + value: 'intrusionDetection', }, all: { - value: "all", + value: 'all', }, }, }; -@ApiTags("AuthenticatedFrontend") -@ApiBearerAuth("CSS-Auth") +@ApiTags('AuthenticatedFrontend') +@ApiBearerAuth('CSS-Auth') @ApiOkResponse() @ApiUnauthorizedResponse({ - description: "Unauthorized", + description: 'Unauthorized', }) @ApiBadRequestResponse({ - description: "Camera id or filter is invalid", + description: 'Camera id or filter is invalid', }) @UseGuards(AuthGuard) @Controller() @@ -60,37 +60,37 @@ export class FrontendController { constructor(private readonly databaseService: DatabaseService) {} @ApiParam(filterParams) - @Get(`:filter(${filters.join("|")})/aggregate`) + @Get(`:filter(${filters.join('|')})/aggregate`) getAggregateValues( - @Param("filter", FiltersValidator) filter: FiltersAvailable, + @Param('filter', FiltersValidator) filter: FiltersAvailable, ) { return this.databaseService.aggregateCamera(filter); } @ApiParam(filterParams) - @Get(`:filter(${filters.join("|")})`) - getValues(@Param("filter", FiltersValidator) filter: FiltersAvailable) { + @Get(`:filter(${filters.join('|')})`) + getValues(@Param('filter', FiltersValidator) filter: FiltersAvailable) { return this.databaseService.getData(filter); } @ApiParam({ - name: "id", - type: "number", - description: "Camera id", + name: 'id', + type: 'number', + description: 'Camera id', example: 1, }) @ApiParam({ - name: "timestamp", - type: "string", - example: "2023-11-23T18:38:35.571Z", + name: 'timestamp', + type: 'string', + example: '2023-11-23T18:38:35.571Z', }) - @Header("Content-Type", "image/jpeg") - @Get("/:id(\\d+)/:timestamp") + @Header('Content-Type', 'image/jpeg') + @Get('/:id(\\d+)/:timestamp') async getImage( - @Param("id", CameraValidator) cameraId: CameraIds, - @Param("timestamp") timestamp: string, + @Param('id', CameraValidator) cameraId: CameraIds, + @Param('timestamp') timestamp: string, ) { - const array = await this.databaseService.getRawDataArray("cameras", { + const array = await this.databaseService.getRawDataArray('cameras', { cameraId: cameraId, timestamp: timestamp, }); diff --git a/Backend/src/app/login.controller.ts b/Backend/src/app/login.controller.ts index 03640db..47f7f17 100644 --- a/Backend/src/app/login.controller.ts +++ b/Backend/src/app/login.controller.ts @@ -2,20 +2,20 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Body, Controller, Header, Post } from "@nestjs/common"; +import { Body, Controller, Header, Post } from '@nestjs/common'; import { ApiBody, ApiCreatedResponse, ApiNotFoundResponse, ApiTags, -} from "@nestjs/swagger"; -import UserDTO from "../user.dto"; -import { DatabaseService } from "../database/database.service"; -import { JwtService } from "@nestjs/jwt"; -import * as process from "process"; +} from '@nestjs/swagger'; +import UserDTO from '../user.dto'; +import { DatabaseService } from '../database/database.service'; +import { JwtService } from '@nestjs/jwt'; +import * as process from 'process'; -@ApiTags("Frontend") -@Controller("/") +@ApiTags('Frontend') +@Controller('/') export class LoginController { constructor( private readonly databaseService: DatabaseService, @@ -24,27 +24,27 @@ export class LoginController { @ApiBody({ type: String, - description: "User", + description: 'User', examples: { a: { - summary: "Existing user", + summary: 'Existing user', value: { name: process.env.CSD_USER, password: process.env.CSD_PASSWORD, }, }, b: { - summary: "Non existing user", - value: { name: "non", password: "Basic" }, + summary: 'Non existing user', + value: { name: 'non', password: 'Basic' }, }, }, }) @ApiCreatedResponse() @ApiNotFoundResponse() - @Header("Content-Type", "application/json") - @Post("login") + @Header('Content-Type', 'application/json') + @Post('login') async login(@Body() user: UserDTO) { - await this.databaseService.getRawDataArray("users", user, "User Not found"); + await this.databaseService.getRawDataArray('users', user, 'User Not found'); return { access_token: await this.jwtService.signAsync(user.name), diff --git a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts index a6c7e48..c37c9a2 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts @@ -2,13 +2,13 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { MachineLearningController } from "./machineLearning.controller"; -import { DatabaseService } from "../../database/database.service"; -import { TelegramService } from "../../telegram/telegram.service"; -import { JwtModule } from "@nestjs/jwt"; +import { Test, TestingModule } from '@nestjs/testing'; +import { MachineLearningController } from './machineLearning.controller'; +import { DatabaseService } from '../../database/database.service'; +import { TelegramService } from '../../telegram/telegram.service'; +import { JwtModule } from '@nestjs/jwt'; -describe("MachineLearningController", () => { +describe('MachineLearningController', () => { let controller: MachineLearningController; beforeEach(async () => { @@ -26,7 +26,7 @@ describe("MachineLearningController", () => { controller = app.get(MachineLearningController); }); - it("should exists", () => { + it('should exists', () => { expect(controller).toBeDefined(); }); diff --git a/Backend/src/app/machineLearning/machineLearning.controller.ts b/Backend/src/app/machineLearning/machineLearning.controller.ts index a24e4c3..ade432b 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.ts @@ -12,10 +12,10 @@ import { UploadedFile, UseGuards, UseInterceptors, -} from "@nestjs/common"; -import { DatabaseService } from "../../database/database.service"; -import { FileInterceptor } from "@nestjs/platform-express"; -import DataType from "../../DataType"; +} from '@nestjs/common'; +import { DatabaseService } from '../../database/database.service'; +import { FileInterceptor } from '@nestjs/platform-express'; +import DataType from '../../DataType'; import { ApiBadRequestResponse, ApiBearerAuth, @@ -26,28 +26,28 @@ import { ApiParam, ApiProperty, ApiTags, -} from "@nestjs/swagger"; +} from '@nestjs/swagger'; import { CameraIds, CameraValidator, -} from "../../validators/camera-id/camera.pipe"; -import { AuthGuard } from "../../auth/auth.guard"; -import { TelegramService } from "../../telegram/telegram.service"; +} from '../../validators/camera-id/camera.pipe'; +import { AuthGuard } from '../../auth/auth.guard'; +import { TelegramService } from '../../telegram/telegram.service'; class ImageUploadDto { @ApiProperty({ - type: "string", - format: "binary", - description: "Image file to upload", + type: 'string', + format: 'binary', + description: 'Image file to upload', }) file: any; } -@ApiTags("Machine Learning") +@ApiTags('Machine Learning') @ApiBadRequestResponse({ - description: "Invalid filter or camera id", + description: 'Invalid filter or camera id', }) -@Controller("/:id(\\d+)") +@Controller('/:id(\\d+)') export class MachineLearningController { constructor( private readonly database: DatabaseService, @@ -55,66 +55,66 @@ export class MachineLearningController { ) {} @ApiOperation({ - description: "Updates the online status of the camera", + description: 'Updates the online status of the camera', }) @ApiParam({ - name: "id", - type: "number", + name: 'id', + type: 'number', example: 1, }) @ApiParam({ - name: "status", - type: "string", + name: 'status', + type: 'string', examples: { online: { - value: "online", + value: 'online', }, offline: { - value: "offline", + value: 'offline', }, }, }) @ApiCreatedResponse() - @ApiBearerAuth("CSS-Auth") + @ApiBearerAuth('CSS-Auth') @UseGuards(AuthGuard) @Post(`:status(online|offline)`) saveStatus( - @Param("id", CameraValidator) cameraId: CameraIds, - @Param("status") status: string, + @Param('id', CameraValidator) cameraId: CameraIds, + @Param('status') status: string, ) { // Following condition could be removed as the path can only be online or offline - if (status.toLowerCase() != "online" && status.toLowerCase() != "offline") + if (status.toLowerCase() != 'online' && status.toLowerCase() != 'offline') throw new BadRequestException(`Invalid status ${status}`); return this.database.addData({ cameraId: cameraId, timestamp: new Date().toISOString(), - online: status.toLowerCase() === "online", + online: status.toLowerCase() === 'online', }); } @ApiOperation({ - description: "Used to send image", + description: 'Used to send image', }) - @ApiBearerAuth("CSS-Auth") - @ApiConsumes("multipart/form-data") + @ApiBearerAuth('CSS-Auth') + @ApiConsumes('multipart/form-data') @ApiBody({ description: `The image file to upload`, type: ImageUploadDto, }) @ApiParam({ - name: "id", - type: "number", + name: 'id', + type: 'number', example: 1, }) // @UseGuards(AuthGuard) - @UseInterceptors(FileInterceptor("file")) + @UseInterceptors(FileInterceptor('file')) @Post() async uploadImage( - @Param("id", CameraValidator) cameraId: CameraIds, + @Param('id', CameraValidator) cameraId: CameraIds, @UploadedFile( new ParseFilePipeBuilder() - .addFileTypeValidator({ fileType: "image/jpeg" }) + .addFileTypeValidator({ fileType: 'image/jpeg' }) .addMaxSizeValidator({ maxSize: 100000, // 100Kb }) diff --git a/Backend/src/auth/auth.guard.spec.ts b/Backend/src/auth/auth.guard.spec.ts index f9433df..7c10573 100644 --- a/Backend/src/auth/auth.guard.spec.ts +++ b/Backend/src/auth/auth.guard.spec.ts @@ -1,7 +1,7 @@ -import { JwtService } from "@nestjs/jwt"; +import { JwtService } from '@nestjs/jwt'; -describe("AuthGuard", () => { - const payload = "complexUserNamePayload"; +describe('AuthGuard', () => { + const payload = 'complexUserNamePayload'; let service: JwtService; let jwtToken: string; @@ -12,20 +12,20 @@ describe("AuthGuard", () => { jwtToken = await service.signAsync(payload); }); - it("Should be defined", () => { + it('Should be defined', () => { expect(service).toBeDefined(); expect(jwtToken).toBeDefined(); }); - it("Get Payload from token", () => { + it('Get Payload from token', () => { const user = service.verify(jwtToken); expect(user).toBe(payload); }); - it("Should fail JwtVerify of another token", () => { + it('Should fail JwtVerify of another token', () => { expect(() => service.verify( - "eyJhbGciOiJIUzI1NiJ9.QmFzaWM.MTnCJYESf5QRL9N8gqn5Di5PEZX8eZB5sN8W4TJTDKF", + 'eyJhbGciOiJIUzI1NiJ9.QmFzaWM.MTnCJYESf5QRL9N8gqn5Di5PEZX8eZB5sN8W4TJTDKF', ), ).toThrow(); }); diff --git a/Backend/src/auth/auth.guard.ts b/Backend/src/auth/auth.guard.ts index 0e6c571..9b3b54d 100644 --- a/Backend/src/auth/auth.guard.ts +++ b/Backend/src/auth/auth.guard.ts @@ -5,9 +5,9 @@ import { HttpException, HttpStatus, Injectable, -} from "@nestjs/common"; -import { JwtService } from "@nestjs/jwt"; -import { Request } from "express"; +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { IncomingHttpHeaders } from 'http'; @Injectable() export class AuthGuard implements CanActivate { @@ -21,24 +21,38 @@ export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); - const token = this.extractTokenFromHeader(request); try { - request["user"] = this.jwtService.verify(token); - token; + const headers = this.getGenericHeaders(context); + + //TODO check if the user is saved + request['user'] = this.checkToken(headers); } catch (e) { throw new HttpException(e.message, HttpStatus.UNAUTHORIZED); } return true; } - private extractTokenFromHeader(request: Request): string { - const [type, token] = request.headers.authorization?.split(" ") ?? []; + // token saved as `Bearer ${token}` + checkToken(headers: Record | IncomingHttpHeaders) { + const token = headers.authorization; - if (type != "Bearer") { - throw new ForbiddenException("No token provided"); + if (token == undefined || !token.startsWith('Bearer ')) { + throw new ForbiddenException('No token provided'); } - return token; + return this.jwtService.verify(token.slice(7, token.length)); + } + + private getGenericHeaders(context: ExecutionContext): Record { + const request = context.switchToHttp().getRequest(); + switch (context.getType()) { + case 'rpc': + throw new HttpException('rpc protocol', HttpStatus.NOT_IMPLEMENTED); + case 'ws': + return request.handshake.headers; + case 'http': + return request.headers; + } } } diff --git a/Backend/src/cameraStream/cameraStream.gateway.spec.ts b/Backend/src/cameraStream/cameraStream.gateway.spec.ts new file mode 100644 index 0000000..04ff551 --- /dev/null +++ b/Backend/src/cameraStream/cameraStream.gateway.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CameraStreamGateway } from './cameraStreamGateway'; + +describe('WebrtcGateway', () => { + let gateway: CameraStreamGateway; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [CameraStreamGateway], + }).compile(); + + gateway = module.get(CameraStreamGateway); + }); + + it('should be defined', () => { + expect(gateway).toBeDefined(); + }); +}); diff --git a/Backend/src/cameraStream/cameraStream.module.ts b/Backend/src/cameraStream/cameraStream.module.ts new file mode 100644 index 0000000..af65b7e --- /dev/null +++ b/Backend/src/cameraStream/cameraStream.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { CameraStreamGateway } from './cameraStreamGateway'; +import { JwtModule } from '@nestjs/jwt'; + +@Module({ + imports: [ + JwtModule.register({ + global: true, + secret: process.env.JWT_SECRET, + // FIXME Error: Payload as string is not allowed with the following sign options: expiresIn + // signOptions: { expiresIn: "60s" }, + }), + ], + providers: [CameraStreamGateway], +}) +export class CameraStreamModule {} diff --git a/Backend/src/cameraStream/cameraStreamGateway.ts b/Backend/src/cameraStream/cameraStreamGateway.ts new file mode 100644 index 0000000..4208a3f --- /dev/null +++ b/Backend/src/cameraStream/cameraStreamGateway.ts @@ -0,0 +1,71 @@ +// websocket.gateway.ts +import { + WebSocketGateway, + SubscribeMessage, + ConnectedSocket, + MessageBody, + OnGatewayConnection, + WsException, +} from '@nestjs/websockets'; +import { Socket } from 'socket.io'; +import { AuthGuard } from '../auth/auth.guard'; +import { + Catch, + ExecutionContext, + HttpException, + UseFilters, + UseGuards, + ValidationPipe, +} from '@nestjs/common'; +import * as console from 'console'; +import { SocketsContainer } from '@nestjs/websockets/sockets-container'; +import { DatabaseService } from '../database/database.service'; +import { TelegramService } from '../telegram/telegram.service'; +import { JwtService } from '@nestjs/jwt'; + +@Catch(WsException, HttpException) +export class WsExceptionFilter implements WsExceptionFilter { + catch(exception: WsException, host: ExecutionContext) { + host.switchToWs().getClient().disconnect(); + } +} + +type Message = { id: number; data: Buffer }; + +@WebSocketGateway({ + transports: ['websocket'], + cors: { + origin: '*', + }, + namespace: '/', +}) +@UseGuards(AuthGuard) +@UseFilters(WsExceptionFilter) +export class CameraStreamGateway implements OnGatewayConnection { + constructor(private readonly jwtService: JwtService) {} + + handleConnection(@ConnectedSocket() client: Socket) { + try { + new AuthGuard(this.jwtService).checkToken(client.handshake.headers); + } catch (e) { + client.disconnect(); + return; + } + client.join('clients'); + } + + @SubscribeMessage('message') + private broadcastMessage( + @ConnectedSocket() client: Socket, + @MessageBody() data: string, + ) { + try { + const message = JSON.parse(data) as Message; + console.log(message.id, message.data); + + client.to('clients').emit(message.id.toString(), message.data); + } catch (e) { + return e.message; + } + } +} diff --git a/Backend/src/database/database.service.spec.ts b/Backend/src/database/database.service.spec.ts index 271a3f6..9d1f7cb 100644 --- a/Backend/src/database/database.service.spec.ts +++ b/Backend/src/database/database.service.spec.ts @@ -2,10 +2,10 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { DatabaseService } from "./database.service"; +import { Test, TestingModule } from '@nestjs/testing'; +import { DatabaseService } from './database.service'; -describe("DatabaseService", () => { +describe('DatabaseService', () => { let databaseService: DatabaseService; beforeEach(async () => { @@ -16,23 +16,23 @@ describe("DatabaseService", () => { databaseService = module.get(DatabaseService); }); - it("should be defined", () => { + it('should be defined', () => { expect(databaseService).toBeDefined(); }); - it("should get aggregated data", async () => { + it('should get aggregated data', async () => { const aggregateData = await databaseService.aggregateCamera(); expect(aggregateData).not.toBeNull(); }); - it("should get single data", async () => { - const aggregateData = await databaseService.getData("all"); + it('should get single data', async () => { + const aggregateData = await databaseService.getData('all'); expect(aggregateData).not.toBeNull(); }); - it("should get image data", async () => { - const aggregateData = await databaseService.getData("all"); + it('should get image data', async () => { + const aggregateData = await databaseService.getData('all'); aggregateData .filter((value) => value.intrusionDetection) @@ -43,7 +43,7 @@ describe("DatabaseService", () => { ); const spy = jest.fn(); - await databaseService.getImage(1, "this will make it throw").catch(spy); + await databaseService.getImage(1, 'this will make it throw').catch(spy); expect(spy).toHaveBeenCalled(); }); }); diff --git a/Backend/src/database/database.service.ts b/Backend/src/database/database.service.ts index e3b6501..f369556 100644 --- a/Backend/src/database/database.service.ts +++ b/Backend/src/database/database.service.ts @@ -6,14 +6,14 @@ import { Injectable, NotAcceptableException, NotFoundException, -} from "@nestjs/common"; -import { Db, Document, Filter, MatchKeysAndValues, MongoClient } from "mongodb"; -import "dotenv/config"; -import DataType from "../DataType"; -import { CameraIds } from "../validators/camera-id/camera.pipe"; -import { FiltersAvailable } from "../validators/filters/filters.pipe"; -import * as process from "process"; -import UserDTO from "../user.dto"; +} from '@nestjs/common'; +import { Db, Document, Filter, MatchKeysAndValues, MongoClient } from 'mongodb'; +import 'dotenv/config'; +import DataType from '../DataType'; +import { CameraIds } from '../validators/camera-id/camera.pipe'; +import { FiltersAvailable } from '../validators/filters/filters.pipe'; +import * as process from 'process'; +import UserDTO from '../user.dto'; const url = `mongodb://${process.env.MONGO_INITDB_ROOT_USERNAME}:${process.env.MONGO_INITDB_ROOT_PASSWORD}@${process.env.MONGO_HOST}`; @@ -25,9 +25,9 @@ export class DatabaseService { const client = new MongoClient(url); client.connect(); - this.DB = client.db("csd"); + this.DB = client.db('csd'); // If no user exists it automatically creates one with the default credentials in env file - this.DB.collection("users") + this.DB.collection('users') .countDocuments() .then((size) => { if (size == 0) { @@ -45,14 +45,14 @@ export class DatabaseService { } getData(filter?: FiltersAvailable): Promise { - return this.DB.collection("cameras") + return this.DB.collection('cameras') .aggregate([ { $addFields: { intrusionDetection: { $cond: { if: { - $ifNull: ["$intrusionDetection", false], + $ifNull: ['$intrusionDetection', false], }, then: true, else: false, @@ -66,11 +66,11 @@ export class DatabaseService { } aggregateCamera(filter?: FiltersAvailable): Promise { - return this.DB.collection("cameras") + return this.DB.collection('cameras') .aggregate() .match(this.getFilter(filter)) .group({ - _id: "$cameraId", + _id: '$cameraId', count: { $sum: 1, }, @@ -79,7 +79,7 @@ export class DatabaseService { } async getImage(cameraId: number, timestamp: string): Promise { - const array = await this.getRawDataArray("cameras", { + const array = await this.getRawDataArray('cameras', { cameraId: cameraId, timestamp: timestamp, }); @@ -89,9 +89,9 @@ export class DatabaseService { async getRawDataArray( collection: string, filter: Filter = {}, - errorString0: string = "Data Not found", + errorString0: string = 'Data Not found', limit: number = 1, - errorStringExceed: string = "Too much data found", + errorStringExceed: string = 'Too much data found', ) { const array = await this.DB.collection(collection).find(filter).toArray(); @@ -106,9 +106,9 @@ export class DatabaseService { user: Filter, newData: MatchKeysAndValues, ) { - await this.getRawDataArray("users", user, "User Not found"); + await this.getRawDataArray('users', user, 'User Not found'); - return this.DB.collection("users").updateOne(user, { + return this.DB.collection('users').updateOne(user, { $set: newData, }); } @@ -120,19 +120,19 @@ export class DatabaseService { private getFilter(filter?: FiltersAvailable) { switch (filter) { - case "intrusionDetection": + case 'intrusionDetection': return { intrusionDetection: { $eq: true }, }; - case "online": + case 'online': return { online: { $eq: true }, }; - case "offline": + case 'offline': return { online: { $eq: false }, }; - case "all": + case 'all': default: return {}; } diff --git a/Backend/src/env.spec.ts b/Backend/src/env.spec.ts index e0c50dc..df587da 100644 --- a/Backend/src/env.spec.ts +++ b/Backend/src/env.spec.ts @@ -2,26 +2,26 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -describe("FiltersPipe", () => { - it("MONGO_INITDB_ROOT_USERNAME should be defined", () => { +describe('FiltersPipe', () => { + it('MONGO_INITDB_ROOT_USERNAME should be defined', () => { expect(process.env.MONGO_INITDB_ROOT_USERNAME).toBeDefined(); }); - it("MONGO_INITDB_ROOT_PASSWORD should be defined", () => { + it('MONGO_INITDB_ROOT_PASSWORD should be defined', () => { expect(process.env.MONGO_INITDB_ROOT_PASSWORD).toBeDefined(); }); - it("JWT_SECRET should be defined", () => { + it('JWT_SECRET should be defined', () => { expect(process.env.JWT_SECRET).toBeDefined(); }); - it("CSD_USER should be defined", () => { + it('CSD_USER should be defined', () => { expect(process.env.CSD_USER).toBeDefined(); }); - it("CSD_PASSWORD should be defined", () => { + it('CSD_PASSWORD should be defined', () => { expect(process.env.CSD_PASSWORD).toBeDefined(); }); - it("TELEGRAM_TOKEN should be defined", () => { + it('TELEGRAM_TOKEN should be defined', () => { expect(process.env.TELEGRAM_TOKEN).toBeDefined(); }); - it("MONGO_HOST should be defined", () => { + it('MONGO_HOST should be defined', () => { expect(process.env.MONGO_HOST).toBeDefined(); }); }); diff --git a/Backend/src/main.ts b/Backend/src/main.ts index 4f5c2e3..2bde435 100644 --- a/Backend/src/main.ts +++ b/Backend/src/main.ts @@ -2,39 +2,43 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { NestFactory } from "@nestjs/core"; -import { AppModule } from "./app.module"; -import { ValidationPipe } from "@nestjs/common"; -import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { IoAdapter } from '@nestjs/platform-socket.io'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); + // app.useWebSocketAdapter(new WsAdapter(app)); + app.useWebSocketAdapter(new IoAdapter(app)); const config = new DocumentBuilder() - .setTitle("Complex System Design") - .setVersion("1.0") - .setDescription("The backend API description") - .addServer("", "localhost") + .setTitle('Complex System Design') + .setVersion('1.0') + .setDescription('The backend API description') + .addServer('', 'localhost') .addBearerAuth( { - type: "http", - scheme: "bearer", - bearerFormat: "JWT", - name: "JWT", - description: "Enter your JWT token", - in: "header", + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'JWT', + description: 'Enter your JWT token', + in: 'header', }, - "CSS-Auth", + 'CSS-Auth', ) .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup("swagger-api", app, document); + SwaggerModule.setup('swagger-api', app, document); await app.listen(8080); console.log( - "\nApp started, look at http://localhost:8080/swagger-api for the documentation", + '\nApp started, look at http://localhost:8080/swagger-api for the documentation', ); } + bootstrap(); diff --git a/Backend/src/telegram/telegram.service.ts b/Backend/src/telegram/telegram.service.ts index 4f4be9a..d0706a3 100644 --- a/Backend/src/telegram/telegram.service.ts +++ b/Backend/src/telegram/telegram.service.ts @@ -2,11 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Injectable, NotFoundException } from "@nestjs/common"; -import * as TelegramBot from "node-telegram-bot-api"; -import * as process from "process"; -import { DatabaseService } from "../database/database.service"; -import UserDTO from "../user.dto"; +import { Injectable, NotFoundException } from '@nestjs/common'; +import * as TelegramBot from 'node-telegram-bot-api'; +import * as process from 'process'; +import { DatabaseService } from '../database/database.service'; +import UserDTO from '../user.dto'; @Injectable() export class TelegramService { @@ -24,8 +24,8 @@ export class TelegramService { private async welcome(msg: TelegramBot.Message) { await this.bot.sendMessage( msg.chat.id, - "Welcome to CSS bot\n" + - "This bot was developed by Leonardo Migliorelli\n" + + 'Welcome to CSS bot\n' + + 'This bot was developed by Leonardo Migliorelli\n' + "Please login with '/user '\n" + "To disable intrusion detection, type '/disable'" + "To reenable intrusion detection, type '/enable'", @@ -34,7 +34,7 @@ export class TelegramService { private async onLogin(msg: TelegramBot.Message) { // removes /start command and then format name and password - const array = msg.text.substring(6).split(" "); + const array = msg.text.substring(6).split(' '); const userData: UserDTO = { name: array[0], password: array[1], @@ -74,13 +74,13 @@ export class TelegramService { } catch (e) { await this.bot.sendMessage( msg.chat.id, - e == NotFoundException ? "You are not logged in" : e.message, + e == NotFoundException ? 'You are not logged in' : e.message, ); return; } await this.bot.sendMessage( msg.chat.id, - `Intrusion detection ${status ? "enabled" : "disabled"}`, + `Intrusion detection ${status ? 'enabled' : 'disabled'}`, ); return; } @@ -90,7 +90,7 @@ export class TelegramService { date: Date, image: Buffer, ) { - const users = await this.databaseService.getRawDataArray("users"); + const users = await this.databaseService.getRawDataArray('users'); users .filter((user) => user.getsAlerts) diff --git a/Backend/src/user.dto.ts b/Backend/src/user.dto.ts index a3243b0..5619f14 100644 --- a/Backend/src/user.dto.ts +++ b/Backend/src/user.dto.ts @@ -2,7 +2,7 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { IsString } from "class-validator"; +import { IsString } from 'class-validator'; export default class UserDTO { @IsString() diff --git a/Backend/src/validators/camera-id/camera.pipe.spec.ts b/Backend/src/validators/camera-id/camera.pipe.spec.ts index 28c0517..4dbd627 100644 --- a/Backend/src/validators/camera-id/camera.pipe.spec.ts +++ b/Backend/src/validators/camera-id/camera.pipe.spec.ts @@ -1,12 +1,12 @@ -import { cameraIds, CameraValidator } from "./camera.pipe"; +import { cameraIds, CameraValidator } from './camera.pipe'; -describe("CameraIdPipe", () => { +describe('CameraIdPipe', () => { const validator = new CameraValidator(); - it("should throw as not correct value", () => { - expect(() => validator.transform("200")).toThrow(); + it('should throw as not correct value', () => { + expect(() => validator.transform('200')).toThrow(); }); - it("should accept all the values", () => { + it('should accept all the values', () => { cameraIds.forEach((i) => expect(validator.transform(`${i}`)).toBe(i)); }); }); diff --git a/Backend/src/validators/camera-id/camera.pipe.ts b/Backend/src/validators/camera-id/camera.pipe.ts index e561411..5570e6c 100644 --- a/Backend/src/validators/camera-id/camera.pipe.ts +++ b/Backend/src/validators/camera-id/camera.pipe.ts @@ -3,7 +3,7 @@ import { HttpStatus, Injectable, PipeTransform, -} from "@nestjs/common"; +} from '@nestjs/common'; @Injectable() export class CameraValidator implements PipeTransform { @@ -12,7 +12,7 @@ export class CameraValidator implements PipeTransform { if (!cameraIds.includes(cameraId)) { throw new HttpException( - "Invalid camera Id " + cameraId, + 'Invalid camera Id ' + cameraId, HttpStatus.BAD_REQUEST, ); } diff --git a/Backend/src/validators/filters/filters.pipe.spec.ts b/Backend/src/validators/filters/filters.pipe.spec.ts index cb796de..7901b57 100644 --- a/Backend/src/validators/filters/filters.pipe.spec.ts +++ b/Backend/src/validators/filters/filters.pipe.spec.ts @@ -1,12 +1,12 @@ -import { filters, FiltersValidator } from "./filters.pipe"; +import { filters, FiltersValidator } from './filters.pipe'; -describe("FiltersPipe", () => { +describe('FiltersPipe', () => { const validator = new FiltersValidator(); - it("should throw as not correct value", () => { - expect(() => validator.transform("this throws error")).toThrow(); + it('should throw as not correct value', () => { + expect(() => validator.transform('this throws error')).toThrow(); }); - it("should accept all the values", () => { + it('should accept all the values', () => { filters.forEach((f) => { expect(validator.transform(f)).toBeDefined(); }); diff --git a/Backend/src/validators/filters/filters.pipe.ts b/Backend/src/validators/filters/filters.pipe.ts index 0772a12..7420d48 100644 --- a/Backend/src/validators/filters/filters.pipe.ts +++ b/Backend/src/validators/filters/filters.pipe.ts @@ -3,7 +3,7 @@ import { HttpStatus, Injectable, PipeTransform, -} from "@nestjs/common"; +} from '@nestjs/common'; @Injectable() export class FiltersValidator implements PipeTransform { @@ -20,9 +20,9 @@ export class FiltersValidator implements PipeTransform { } export const filters = [ - "intrusionDetection", - "online", - "offline", - "all", + 'intrusionDetection', + 'online', + 'offline', + 'all', ] as const; export type FiltersAvailable = (typeof filters)[number]; diff --git a/Backend/test/app.e2e-spec.ts b/Backend/test/app.e2e-spec.ts index ffddb8c..7db7b53 100644 --- a/Backend/test/app.e2e-spec.ts +++ b/Backend/test/app.e2e-spec.ts @@ -2,11 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { INestApplication } from "@nestjs/common"; -import { AppModule } from "../src/app.module"; +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { AppModule } from '../src/app.module'; -describe("MachinelearningController (e2e)", () => { +describe('MachinelearningController (e2e)', () => { let app: INestApplication; beforeEach(async () => { @@ -18,7 +18,7 @@ describe("MachinelearningController (e2e)", () => { await app.init(); }); - it("/ (GET)", () => { + it('/ (GET)', () => { // return request(app.getHttpServer()) // .get('/') // .expect(200) From 0425ce04cc60ddf68c7a8b4cdcc59dde5addd549 Mon Sep 17 00:00:00 2001 From: Glydric Date: Tue, 5 Dec 2023 19:07:30 +0100 Subject: [PATCH 07/12] improvements --- Backend/src/cameraStream/cameraStreamGateway.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Backend/src/cameraStream/cameraStreamGateway.ts b/Backend/src/cameraStream/cameraStreamGateway.ts index 4208a3f..4fb8cd8 100644 --- a/Backend/src/cameraStream/cameraStreamGateway.ts +++ b/Backend/src/cameraStream/cameraStreamGateway.ts @@ -15,12 +15,8 @@ import { HttpException, UseFilters, UseGuards, - ValidationPipe, } from '@nestjs/common'; import * as console from 'console'; -import { SocketsContainer } from '@nestjs/websockets/sockets-container'; -import { DatabaseService } from '../database/database.service'; -import { TelegramService } from '../telegram/telegram.service'; import { JwtService } from '@nestjs/jwt'; @Catch(WsException, HttpException) From 47cf35a86a2d848271b7a1168808b338b99b4aa8 Mon Sep 17 00:00:00 2001 From: Glydric Date: Tue, 5 Dec 2023 19:14:27 +0100 Subject: [PATCH 08/12] removed useless --- Backend/src/app.module.ts | 7 +++---- .../cameraStream/cameraStream.gateway.spec.ts | 2 +- ...aStreamGateway.ts => cameraStream.gateway.ts} | 0 Backend/src/cameraStream/cameraStream.module.ts | 16 ---------------- 4 files changed, 4 insertions(+), 21 deletions(-) rename Backend/src/cameraStream/{cameraStreamGateway.ts => cameraStream.gateway.ts} (100%) delete mode 100644 Backend/src/cameraStream/cameraStream.module.ts diff --git a/Backend/src/app.module.ts b/Backend/src/app.module.ts index 92466c3..acb1639 100644 --- a/Backend/src/app.module.ts +++ b/Backend/src/app.module.ts @@ -2,14 +2,14 @@ * Copyright (c) 2023. Leonardo Migliorelli */ +import { JwtModule } from '@nestjs/jwt'; import { Module } from '@nestjs/common'; import { MachineLearningController } from './app/machineLearning/machineLearning.controller'; import { FrontendController } from './app/frontend/frontend.controller'; import { DatabaseService } from './database/database.service'; import { TelegramService } from './telegram/telegram.service'; -import { JwtModule } from '@nestjs/jwt'; import { LoginController } from './app/login.controller'; -import { CameraStreamModule } from './cameraStream/cameraStream.module'; +import { CameraStreamGateway } from './cameraStream/cameraStream.gateway'; @Module({ imports: [ @@ -19,10 +19,9 @@ import { CameraStreamModule } from './cameraStream/cameraStream.module'; // FIXME Error: Payload as string is not allowed with the following sign options: expiresIn // signOptions: { expiresIn: "60s" }, }), - CameraStreamModule, ], controllers: [MachineLearningController, FrontendController, LoginController], - providers: [DatabaseService, TelegramService], + providers: [DatabaseService, TelegramService, CameraStreamGateway], exports: [DatabaseService, TelegramService], }) export class AppModule {} diff --git a/Backend/src/cameraStream/cameraStream.gateway.spec.ts b/Backend/src/cameraStream/cameraStream.gateway.spec.ts index 04ff551..a806383 100644 --- a/Backend/src/cameraStream/cameraStream.gateway.spec.ts +++ b/Backend/src/cameraStream/cameraStream.gateway.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { CameraStreamGateway } from './cameraStreamGateway'; +import { CameraStreamGateway } from './cameraStream.gateway'; describe('WebrtcGateway', () => { let gateway: CameraStreamGateway; diff --git a/Backend/src/cameraStream/cameraStreamGateway.ts b/Backend/src/cameraStream/cameraStream.gateway.ts similarity index 100% rename from Backend/src/cameraStream/cameraStreamGateway.ts rename to Backend/src/cameraStream/cameraStream.gateway.ts diff --git a/Backend/src/cameraStream/cameraStream.module.ts b/Backend/src/cameraStream/cameraStream.module.ts deleted file mode 100644 index af65b7e..0000000 --- a/Backend/src/cameraStream/cameraStream.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { CameraStreamGateway } from './cameraStreamGateway'; -import { JwtModule } from '@nestjs/jwt'; - -@Module({ - imports: [ - JwtModule.register({ - global: true, - secret: process.env.JWT_SECRET, - // FIXME Error: Payload as string is not allowed with the following sign options: expiresIn - // signOptions: { expiresIn: "60s" }, - }), - ], - providers: [CameraStreamGateway], -}) -export class CameraStreamModule {} From abeb18c006a44b448597fa2c9d951048142cec74 Mon Sep 17 00:00:00 2001 From: Glydric Date: Wed, 6 Dec 2023 10:05:03 +0100 Subject: [PATCH 09/12] fixed authguard test, added backend workflow test and created a global test --- .github/workflows/backend.yml | 18 ++++++++ Backend/jest.config.json | 18 ++++++++ Backend/package.json | 19 +------- Backend/src/auth/auth.guard.spec.ts | 26 +++++++---- .../src/cameraStream/cameraStream.gateway.ts | 2 - Backend/src/env.spec.ts | 46 +++++++++---------- 6 files changed, 78 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/backend.yml create mode 100644 Backend/jest.config.json diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 0000000..fde4611 --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,18 @@ +on: + push: + pull_request: + branches: + - main + +jobs: + jest-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Working Directory + run: cd Backend + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm test diff --git a/Backend/jest.config.json b/Backend/jest.config.json new file mode 100644 index 0000000..5c0cbcd --- /dev/null +++ b/Backend/jest.config.json @@ -0,0 +1,18 @@ +{ + "globalSetup": "./env.spec.ts", + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" +} \ No newline at end of file diff --git a/Backend/package.json b/Backend/package.json index 8fd2285..b5e16f9 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -11,7 +11,7 @@ "start:dev": "npm start -- --watch", "start:debug": "npm start -- --debug --watch", "start:prod": "node dist/main", - "test": "npm ci && jest --detectOpenHandles", + "test": "jest --detectOpenHandles", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", @@ -67,22 +67,5 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" - }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "coverageDirectory": "../coverage", - "testEnvironment": "node" } } diff --git a/Backend/src/auth/auth.guard.spec.ts b/Backend/src/auth/auth.guard.spec.ts index 7c10573..be99f16 100644 --- a/Backend/src/auth/auth.guard.spec.ts +++ b/Backend/src/auth/auth.guard.spec.ts @@ -1,32 +1,42 @@ import { JwtService } from '@nestjs/jwt'; +import { AuthGuard } from './auth.guard'; describe('AuthGuard', () => { const payload = 'complexUserNamePayload'; let service: JwtService; - let jwtToken: string; + let auth: AuthGuard; + let headers: Record; beforeAll(async () => { service = new JwtService({ - secret: process.env.JWT_SECRET, + secret: process.env.JWT_SECRET ?? 'keytest', }); - jwtToken = await service.signAsync(payload); + + auth = new AuthGuard(service); + headers = tokenToHeaders(await service.signAsync(payload)); }); it('Should be defined', () => { - expect(service).toBeDefined(); - expect(jwtToken).toBeDefined(); + expect(auth).toBeDefined(); + expect(headers).toBeDefined(); }); it('Get Payload from token', () => { - const user = service.verify(jwtToken); + const user = auth.checkToken(headers); expect(user).toBe(payload); }); it('Should fail JwtVerify of another token', () => { expect(() => - service.verify( - 'eyJhbGciOiJIUzI1NiJ9.QmFzaWM.MTnCJYESf5QRL9N8gqn5Di5PEZX8eZB5sN8W4TJTDKF', + auth.checkToken( + tokenToHeaders( + 'eyJhbGciOiJIUzI1NiJ9.QmFzaWM.MTnCJYESf5QRL9N8gqn5Di5PEZX8eZB5sN8W4TJTDKF', + ), ), ).toThrow(); }); + + function tokenToHeaders(token: string) { + return { authorization: `Bearer ${token}` }; + } }); diff --git a/Backend/src/cameraStream/cameraStream.gateway.ts b/Backend/src/cameraStream/cameraStream.gateway.ts index 4fb8cd8..b5ddafc 100644 --- a/Backend/src/cameraStream/cameraStream.gateway.ts +++ b/Backend/src/cameraStream/cameraStream.gateway.ts @@ -16,7 +16,6 @@ import { UseFilters, UseGuards, } from '@nestjs/common'; -import * as console from 'console'; import { JwtService } from '@nestjs/jwt'; @Catch(WsException, HttpException) @@ -57,7 +56,6 @@ export class CameraStreamGateway implements OnGatewayConnection { ) { try { const message = JSON.parse(data) as Message; - console.log(message.id, message.data); client.to('clients').emit(message.id.toString(), message.data); } catch (e) { diff --git a/Backend/src/env.spec.ts b/Backend/src/env.spec.ts index df587da..8d47c48 100644 --- a/Backend/src/env.spec.ts +++ b/Backend/src/env.spec.ts @@ -2,26 +2,26 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -describe('FiltersPipe', () => { - it('MONGO_INITDB_ROOT_USERNAME should be defined', () => { - expect(process.env.MONGO_INITDB_ROOT_USERNAME).toBeDefined(); - }); - it('MONGO_INITDB_ROOT_PASSWORD should be defined', () => { - expect(process.env.MONGO_INITDB_ROOT_PASSWORD).toBeDefined(); - }); - it('JWT_SECRET should be defined', () => { - expect(process.env.JWT_SECRET).toBeDefined(); - }); - it('CSD_USER should be defined', () => { - expect(process.env.CSD_USER).toBeDefined(); - }); - it('CSD_PASSWORD should be defined', () => { - expect(process.env.CSD_PASSWORD).toBeDefined(); - }); - it('TELEGRAM_TOKEN should be defined', () => { - expect(process.env.TELEGRAM_TOKEN).toBeDefined(); - }); - it('MONGO_HOST should be defined', () => { - expect(process.env.MONGO_HOST).toBeDefined(); - }); -}); +import * as process from 'process'; + +function throwsIfNull(value: any, message: string) { + if (!value) { + throw new Error(message); + } +} + +export default () => { + throwsIfNull( + process.env.MONGO_INITDB_ROOT_USERNAME, + 'MONGO_INITDB_ROOT_USERNAME not defined', + ); + throwsIfNull( + process.env.MONGO_INITDB_ROOT_PASSWORD, + 'MONGO_INITDB_ROOT_PASSWORD not defined', + ); + throwsIfNull(process.env.JWT_SECRET, 'JWT_SECRET not defined'); + throwsIfNull(process.env.CSD_USER, 'CSD_USER not defined'); + throwsIfNull(process.env.CSD_PASSWORD, 'CSD_PASSWORD not defined'); + throwsIfNull(process.env.TELEGRAM_TOKEN, 'TELEGRAM_TOKEN not defined'); + throwsIfNull(process.env.MONGO_HOST, 'MONGO_HOST not defined'); +}; From 8625bb7789eeeb9a22120b3c04449be05365ee91 Mon Sep 17 00:00:00 2001 From: Glydric Date: Wed, 6 Dec 2023 15:28:23 +0100 Subject: [PATCH 10/12] fixed authguard test, added backend workflow test and created a global test --- .github/workflows/backend.yml | 4 +-- Backend/jest.config.json | 2 +- Backend/package.json | 5 ++-- .../machineLearning.controller.spec.ts | 8 +----- .../machineLearning.controller.ts | 3 ++- .../cameraStream/cameraStream.gateway.spec.ts | 9 ++++++- Backend/src/database/database.service.spec.ts | 1 + Backend/src/database/database.service.ts | 6 +++-- Backend/src/env.spec.ts | 27 ------------------- Backend/src/envCheck.ts | 27 +++++++++++++++++++ README.md | 2 ++ docker-compose.yml | 4 +-- 12 files changed, 53 insertions(+), 45 deletions(-) delete mode 100644 Backend/src/env.spec.ts create mode 100644 Backend/src/envCheck.ts diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index fde4611..9728a47 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -10,9 +10,9 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Working Directory - run: cd Backend - name: Install dependencies + working-directory: "Backend" run: npm ci - name: Run tests + working-directory: "Backend" run: npm test diff --git a/Backend/jest.config.json b/Backend/jest.config.json index 5c0cbcd..a320b8d 100644 --- a/Backend/jest.config.json +++ b/Backend/jest.config.json @@ -1,5 +1,5 @@ { - "globalSetup": "./env.spec.ts", + "globalSetup": "./envCheck.ts", "moduleFileExtensions": [ "js", "json", diff --git a/Backend/package.json b/Backend/package.json index b5e16f9..a0c1920 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -8,10 +8,11 @@ "scripts": { "build": "nest build", "start": "npm ci && nest start", - "start:dev": "npm start -- --watch", + "start:dev": "nest start -- --watch", "start:debug": "npm start -- --debug --watch", "start:prod": "node dist/main", - "test": "jest --detectOpenHandles", + "test": "jest", + "test:det": "jest --detectOpenHandles", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", diff --git a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts index c37c9a2..70d8477 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts @@ -5,7 +5,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { MachineLearningController } from './machineLearning.controller'; import { DatabaseService } from '../../database/database.service'; -import { TelegramService } from '../../telegram/telegram.service'; import { JwtModule } from '@nestjs/jwt'; describe('MachineLearningController', () => { @@ -20,7 +19,7 @@ describe('MachineLearningController', () => { }), ], controllers: [MachineLearningController], - providers: [DatabaseService, TelegramService], + providers: [DatabaseService], }).compile(); controller = app.get(MachineLearningController); @@ -29,9 +28,4 @@ describe('MachineLearningController', () => { it('should exists', () => { expect(controller).toBeDefined(); }); - - // afterAll(async () => { - // // await controller.telegramApi.bot.stopPolling(); - // // await controller.telegramApi.bot.close() - // }); }); diff --git a/Backend/src/app/machineLearning/machineLearning.controller.ts b/Backend/src/app/machineLearning/machineLearning.controller.ts index ade432b..a4f3171 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.ts @@ -6,6 +6,7 @@ import { BadRequestException, Controller, HttpStatus, + Optional, Param, ParseFilePipeBuilder, Post, @@ -51,7 +52,7 @@ class ImageUploadDto { export class MachineLearningController { constructor( private readonly database: DatabaseService, - readonly telegramApi: TelegramService, + @Optional() private readonly telegramApi: TelegramService, ) {} @ApiOperation({ diff --git a/Backend/src/cameraStream/cameraStream.gateway.spec.ts b/Backend/src/cameraStream/cameraStream.gateway.spec.ts index a806383..d1b0196 100644 --- a/Backend/src/cameraStream/cameraStream.gateway.spec.ts +++ b/Backend/src/cameraStream/cameraStream.gateway.spec.ts @@ -1,12 +1,19 @@ import { Test, TestingModule } from '@nestjs/testing'; import { CameraStreamGateway } from './cameraStream.gateway'; +import { JwtModule } from '@nestjs/jwt'; -describe('WebrtcGateway', () => { +describe('WebStreamGateway', () => { let gateway: CameraStreamGateway; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [CameraStreamGateway], + imports: [ + JwtModule.register({ + global: true, + secret: process.env.JWT_SECRET, + }), + ], }).compile(); gateway = module.get(CameraStreamGateway); diff --git a/Backend/src/database/database.service.spec.ts b/Backend/src/database/database.service.spec.ts index 9d1f7cb..b0b9e76 100644 --- a/Backend/src/database/database.service.spec.ts +++ b/Backend/src/database/database.service.spec.ts @@ -31,6 +31,7 @@ describe('DatabaseService', () => { expect(aggregateData).not.toBeNull(); }); + it('should get image data', async () => { const aggregateData = await databaseService.getData('all'); diff --git a/Backend/src/database/database.service.ts b/Backend/src/database/database.service.ts index f369556..bcc04c0 100644 --- a/Backend/src/database/database.service.ts +++ b/Backend/src/database/database.service.ts @@ -15,16 +15,18 @@ import { FiltersAvailable } from '../validators/filters/filters.pipe'; import * as process from 'process'; import UserDTO from '../user.dto'; -const url = `mongodb://${process.env.MONGO_INITDB_ROOT_USERNAME}:${process.env.MONGO_INITDB_ROOT_PASSWORD}@${process.env.MONGO_HOST}`; +const url = `${process.env.MONGO_PROTOCOL ?? 'mongodb'}://${ + process.env.MONGO_INITDB_ROOT_USERNAME +}:${process.env.MONGO_INITDB_ROOT_PASSWORD}@${process.env.MONGO_HOST}`; @Injectable() export class DatabaseService { private DB: Db; constructor() { + console.log(url); const client = new MongoClient(url); - client.connect(); this.DB = client.db('csd'); // If no user exists it automatically creates one with the default credentials in env file this.DB.collection('users') diff --git a/Backend/src/env.spec.ts b/Backend/src/env.spec.ts deleted file mode 100644 index 8d47c48..0000000 --- a/Backend/src/env.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023. Leonardo Migliorelli - */ - -import * as process from 'process'; - -function throwsIfNull(value: any, message: string) { - if (!value) { - throw new Error(message); - } -} - -export default () => { - throwsIfNull( - process.env.MONGO_INITDB_ROOT_USERNAME, - 'MONGO_INITDB_ROOT_USERNAME not defined', - ); - throwsIfNull( - process.env.MONGO_INITDB_ROOT_PASSWORD, - 'MONGO_INITDB_ROOT_PASSWORD not defined', - ); - throwsIfNull(process.env.JWT_SECRET, 'JWT_SECRET not defined'); - throwsIfNull(process.env.CSD_USER, 'CSD_USER not defined'); - throwsIfNull(process.env.CSD_PASSWORD, 'CSD_PASSWORD not defined'); - throwsIfNull(process.env.TELEGRAM_TOKEN, 'TELEGRAM_TOKEN not defined'); - throwsIfNull(process.env.MONGO_HOST, 'MONGO_HOST not defined'); -}; diff --git a/Backend/src/envCheck.ts b/Backend/src/envCheck.ts new file mode 100644 index 0000000..00838c2 --- /dev/null +++ b/Backend/src/envCheck.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023. Leonardo Migliorelli + */ + +import * as process from 'process'; + +function throwsIfNotDefined(value: any, message: string) { + if (!value) { + throw new Error(message + ' not Defined'); + } +} + +export default () => { + throwsIfNotDefined( + process.env.MONGO_INITDB_ROOT_USERNAME, + 'MONGO_INITDB_ROOT_USERNAME', + ); + throwsIfNotDefined( + process.env.MONGO_INITDB_ROOT_PASSWORD, + 'MONGO_INITDB_ROOT_PASSWORD', + ); + throwsIfNotDefined(process.env.JWT_SECRET, 'JWT_SECRET'); + throwsIfNotDefined(process.env.CSD_USER, 'CSD_USER'); + throwsIfNotDefined(process.env.CSD_PASSWORD, 'CSD_PASSWORD'); + throwsIfNotDefined(process.env.TELEGRAM_TOKEN, 'TELEGRAM_TOKEN'); + throwsIfNotDefined(process.env.MONGO_HOST, 'MONGO_HOST'); +}; diff --git a/README.md b/README.md index d0bfb65..a8e8f14 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ The repository contains the sources code for the Camera Security System project. Firstly add .env file to root directory using the following template ```text +MONGO_PROTOCOL=mongodb +MONGO_HOST=mongo:27017 MONGO_INITDB_ROOT_USERNAME=username MONGO_INITDB_ROOT_PASSWORD=password JWT_SECRET=secret diff --git a/docker-compose.yml b/docker-compose.yml index b8d8c9c..2398b74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: - ./Backend:/home/node env_file: - .env - environment: - - MONGO_HOST=mongo:27017 # this indicates the container called mongo and the port it is listening on +# environment: +# - MONGO_HOST=mongo:27017 # this indicates the container called mongo and the port it is listening on # command: npm start command: npm run start:dev From aa3a872c79d4fb7e67cb94e6c67bd5c6a1fefa5c Mon Sep 17 00:00:00 2001 From: Glydric Date: Wed, 6 Dec 2023 15:49:48 +0100 Subject: [PATCH 11/12] using secrets --- .github/workflows/backend.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 9728a47..cc89e23 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -1,5 +1,8 @@ on: push: + branches: + - main + - glydric pull_request: branches: - main @@ -10,6 +13,15 @@ jobs: steps: - uses: actions/checkout@v2 + env: + MONGO_PROTOCOL: ${{secrets.MONGO_PROTOCOL}} + MONGO_HOST: ${{secrets.MONGO_HOST}} + MONGO_INITDB_ROOT_USERNAME: ${{secrets.MONGO_INITDB_ROOT_USERNAME}} + MONGO_INITDB_ROOT_PASSWORD: ${{secrets.MONGO_INITDB_ROOT_PASSWORD}} + JWT_SECRET: ${{secrets.JWT_SECRET}} + CSD_USER: ${{secrets.CSD_USER}} + CSD_PASSWORD: ${{secrets.CSD_PASSWORD}} + TELEGRAM_TOKEN: ${{secrets.TELEGRAM_TOKEN}} - name: Install dependencies working-directory: "Backend" run: npm ci From cc7da138c62ab2efa78412ebea530cb18289fd92 Mon Sep 17 00:00:00 2001 From: Glydric Date: Wed, 6 Dec 2023 15:52:53 +0100 Subject: [PATCH 12/12] fixes --- .github/workflows/backend.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index cc89e23..24f4639 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -13,18 +13,18 @@ jobs: steps: - uses: actions/checkout@v2 - env: - MONGO_PROTOCOL: ${{secrets.MONGO_PROTOCOL}} - MONGO_HOST: ${{secrets.MONGO_HOST}} - MONGO_INITDB_ROOT_USERNAME: ${{secrets.MONGO_INITDB_ROOT_USERNAME}} - MONGO_INITDB_ROOT_PASSWORD: ${{secrets.MONGO_INITDB_ROOT_PASSWORD}} - JWT_SECRET: ${{secrets.JWT_SECRET}} - CSD_USER: ${{secrets.CSD_USER}} - CSD_PASSWORD: ${{secrets.CSD_PASSWORD}} - TELEGRAM_TOKEN: ${{secrets.TELEGRAM_TOKEN}} - name: Install dependencies working-directory: "Backend" run: npm ci - name: Run tests working-directory: "Backend" run: npm test + env: + MONGO_PROTOCOL: ${{ secrets.MONGO_PROTOCOL }} + MONGO_HOST: ${{ secrets.MONGO_HOST }} + MONGO_INITDB_ROOT_USERNAME: ${{ secrets.MONGO_INITDB_ROOT_USERNAME }} + MONGO_INITDB_ROOT_PASSWORD: ${{ secrets.MONGO_INITDB_ROOT_PASSWORD }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + CSD_USER: ${{ secrets.CSD_USER }} + CSD_PASSWORD: ${{ secrets.CSD_PASSWORD }} + TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}