├── src ├── infrastructure │ ├── web │ │ ├── router │ │ │ ├── controllers │ │ │ │ ├── CameraController.js │ │ │ │ ├── AdministrationController.js │ │ │ │ ├── SurvAPIController.js │ │ │ │ ├── LogoutController.js │ │ │ │ ├── RegistrationController.js │ │ │ │ └── LoginController.js │ │ │ └── SurvAPIRouter.js │ │ ├── views │ │ │ ├── register.ejs │ │ │ ├── footer.ejs │ │ │ ├── loginForm.ejs │ │ │ ├── registerForm.ejs │ │ │ ├── login.ejs │ │ │ ├── error.ejs │ │ │ ├── index.ejs │ │ │ ├── micromodal.ejs │ │ │ ├── head.ejs │ │ │ ├── camera.ejs │ │ │ ├── nav.ejs │ │ │ └── addCamera.ejs │ │ └── static │ │ │ ├── style.css │ │ │ ├── micromodal.css │ │ │ └── require.js │ ├── security │ │ └── SessionManager.js │ └── persistence │ │ ├── sequelize │ │ ├── SequelizeUserRepository.js │ │ ├── SequelizeCameraRepository.js │ │ └── SequelizeDetectionRepository.js │ │ └── SequelizeDatabaseConnection.js ├── Dockerfile ├── domain │ └── model │ │ ├── detection │ │ ├── VideoAnalysisService.js │ │ ├── DetectionDate.js │ │ └── Detection.js │ │ ├── camera │ │ ├── CameraConfiguration.js │ │ └── Camera.js │ │ ├── fakerepositories │ │ └── DetectionRepository.js │ │ ├── services │ │ ├── AuthenticationService.ts │ │ └── UserService.ts │ │ ├── frontend │ │ ├── CameraListRenderer.js │ │ └── LatestDetectionRenderer.js │ │ └── mockDatabase │ │ └── MockDatabase.ts ├── test │ ├── test.js │ └── DummyData.js ├── docker-compose.yml ├── db │ └── entrypoint │ │ └── schema.sql ├── package.json ├── .gitignore ├── SecurityCam.js ├── tsconfig.json └── SurvAPIApplication.js ├── img ├── add.PNG ├── db.PNG ├── ui.png ├── start.PNG ├── addcam1.PNG ├── landing.PNG ├── logo_large.png ├── logo_small.png ├── screenshot.png ├── logo_white_large.png ├── architecture-color.png ├── logo_small_icon_only.png ├── logo_small_icon_only_inverted.png ├── README.adoc └── logo.svg ├── resources.adoc ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── node.js.yml ├── LICENSE ├── index.html └── README.adoc /src/infrastructure/web/router/controllers/CameraController.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/infrastructure/web/router/controllers/AdministrationController.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/add.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/add.PNG -------------------------------------------------------------------------------- /img/db.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/db.PNG -------------------------------------------------------------------------------- /img/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/ui.png -------------------------------------------------------------------------------- /img/start.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/start.PNG -------------------------------------------------------------------------------- /img/addcam1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/addcam1.PNG -------------------------------------------------------------------------------- /img/landing.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/landing.PNG -------------------------------------------------------------------------------- /img/logo_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/logo_large.png -------------------------------------------------------------------------------- /img/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/logo_small.png -------------------------------------------------------------------------------- /img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/screenshot.png -------------------------------------------------------------------------------- /img/logo_white_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/logo_white_large.png -------------------------------------------------------------------------------- /img/architecture-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/architecture-color.png -------------------------------------------------------------------------------- /img/logo_small_icon_only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/logo_small_icon_only.png -------------------------------------------------------------------------------- /img/logo_small_icon_only_inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/HEAD/img/logo_small_icon_only_inverted.png -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:latest 2 | 3 | MAINTAINER me 4 | 5 | ENV MYSQL_DATABASE=SurvAPI \ 6 | MYSQL_ROOT_PASSWORD=example 7 | 8 | ADD schema.sql /docker-entrypoint-initdb.d 9 | 10 | EXPOSE 3306 -------------------------------------------------------------------------------- /src/domain/model/detection/VideoAnalysisService.js: -------------------------------------------------------------------------------- 1 | //TODO: This class will run ML5 and will be responsible for the complete detection. 2 | class VideoAnalysisService { 3 | 4 | constructor() { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /src/domain/model/camera/CameraConfiguration.js: -------------------------------------------------------------------------------- 1 | class CameraConfiguration { 2 | 3 | //TODO: later 4 | static createIPCamera(ipAdr, port, resolution) { 5 | return {ip: ipAdr, port: port, resolution: resolution}; 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | describe('Array', function() { 4 | describe('#indexOf()', function() { 5 | it('should return -1 when the value is not present', function() { 6 | assert.equal([1, 2, 3].indexOf(4), -1); 7 | }); 8 | }); 9 | }); -------------------------------------------------------------------------------- /src/infrastructure/security/SessionManager.js: -------------------------------------------------------------------------------- 1 | exports.checkSession = function checkSession(request) { 2 | sessionTmp = request.session; 3 | 4 | return sessionTmp.username != undefined; 5 | } 6 | 7 | exports.asyncMiddleware = fn => 8 | (req, res, next) => { 9 | Promise.resolve(fn(req, res, next)) 10 | .catch(next); 11 | } -------------------------------------------------------------------------------- /src/domain/model/fakerepositories/DetectionRepository.js: -------------------------------------------------------------------------------- 1 | class DetectionRepository { 2 | 3 | constructor(database) { 4 | this.database = database; 5 | } 6 | 7 | findAllDetections() { 8 | return this.database.db; 9 | } 10 | 11 | save(detection) { 12 | this.database.saveDetection(detection); 13 | } 14 | } -------------------------------------------------------------------------------- /src/infrastructure/web/router/controllers/SurvAPIController.js: -------------------------------------------------------------------------------- 1 | const SessionManager = require("../../../security/SessionManager"); 2 | 3 | exports.index = function(req, res) { 4 | 5 | // if the user is not logged in, the username will be set to 0. 6 | res.render("index.ejs", {username: (SessionManager.checkSession(req)) ? sessionTmp.username : 0}); 7 | } -------------------------------------------------------------------------------- /src/infrastructure/web/router/SurvAPIRouter.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | const SessionManager = require("../../security/SessionManager"); 3 | var survAPIRouter = express.Router(); 4 | 5 | exports.error = function(req, res){ 6 | res.status(404).render("error.ejs", {username: (SessionManager.checkSession(req)) ? sessionTmp.username : 0}); 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/infrastructure/web/router/controllers/LogoutController.js: -------------------------------------------------------------------------------- 1 | // LOGOUT 2 | const SessionManager = require("../../../security/SessionManager"); 3 | 4 | exports.logout = function(req, res) { 5 | if(!SessionManager.checkSession(req)) res.redirect("/"); 6 | 7 | const sessionTmp = req.session; 8 | sessionTmp.destroy(); 9 | res.render('index.ejs', {username: -1}); 10 | }; -------------------------------------------------------------------------------- /src/infrastructure/web/views/register.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('head.ejs', {}) %> 4 | 5 |
6 | <%- include('nav.ejs', {username: 0}) %> 7 | 8 | <%- include('registerForm.ejs', {}) %> 9 | 10 | <%- include('footer.ejs', {}) %> 11 |
12 | 13 | <%- include('micromodal.ejs', {}) %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Use root/example as user/password credentials 2 | version: '3.1' 3 | 4 | services: 5 | 6 | db: 7 | image: mysql 8 | command: --default-authentication-plugin=mysql_native_password 9 | restart: always 10 | ports: 11 | - 3306:3306 12 | environment: 13 | MYSQL_ROOT_PASSWORD: example 14 | volumes: 15 | - schema:/db/entrypoint 16 | volumes: 17 | schema: -------------------------------------------------------------------------------- /src/domain/model/camera/Camera.js: -------------------------------------------------------------------------------- 1 | class Camera { 2 | 3 | constructor(id, name, description, configuration) { 4 | this.id = id; 5 | this.name = name; 6 | this.description = description; 7 | this.configuration = configuration; 8 | } 9 | 10 | getIp() { 11 | return this.configuration.ip; 12 | } 13 | 14 | getPort() { 15 | return this.configuration.port; 16 | } 17 | } -------------------------------------------------------------------------------- /src/infrastructure/web/views/footer.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/infrastructure/persistence/sequelize/SequelizeUserRepository.js: -------------------------------------------------------------------------------- 1 | const SequelizeDatabaseConnection = require("../SequelizeDatabaseConnection"); 2 | 3 | const repository = SequelizeDatabaseConnection.repository; 4 | 5 | const DataTypes = SequelizeDatabaseConnection.datatypes; 6 | 7 | exports.User = repository.define("user", { 8 | id: { 9 | primaryKey: true, 10 | type: DataTypes.INTEGER 11 | }, 12 | username: DataTypes.TEXT, 13 | password: DataTypes.TEXT, 14 | role: DataTypes.INTEGER 15 | }); -------------------------------------------------------------------------------- /resources.adoc: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | Short collection of resources I want to apply to the project: 4 | 5 | ## 1. Security: 6 | 7 | - https://codeforgeek.com/manage-session-using-node-js-express-4/[Node Session] 8 | - https://entwickler.de/online/javascript/node-js-bcrypt-579817084.html[bcrypt] 9 | 10 | ## 2. Frontend: 11 | 12 | - foo 13 | - bar 14 | 15 | ## 3. Architecture: 16 | 17 | - baz 18 | - foo 19 | 20 | ## 4. ML 21 | 22 | - https://www.kdnuggets.com/2018/09/object-detection-image-classification-yolo.html[YOLO] 23 | - https://learn.ml5js.org/#/reference/index[ML5 Docs] 24 | -------------------------------------------------------------------------------- /src/infrastructure/persistence/sequelize/SequelizeCameraRepository.js: -------------------------------------------------------------------------------- 1 | const SequelizeDatabaseConnection = require("../SequelizeDatabaseConnection"); 2 | 3 | const repository = SequelizeDatabaseConnection.repository; 4 | 5 | const DataTypes = SequelizeDatabaseConnection.datatypes; 6 | 7 | exports.Camera = repository.define("camera", { 8 | id: { 9 | primaryKey: true, 10 | type: DataTypes.BIGINT 11 | }, 12 | name: DataTypes.TEXT, 13 | description: DataTypes.TEXT, 14 | ip: DataTypes.TEXT, 15 | port: DataTypes.SMALLINT, // maximum port is 65535 16 | resolution: DataTypes.SMALLINT 17 | }); -------------------------------------------------------------------------------- /src/infrastructure/web/views/loginForm.ejs: -------------------------------------------------------------------------------- 1 |

Login

2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 |
15 |
-------------------------------------------------------------------------------- /src/domain/model/detection/DetectionDate.js: -------------------------------------------------------------------------------- 1 | class DetectionDate extends Date { 2 | 3 | constructor(day,month,year) { 4 | super(); 5 | if(day == null || month == null || year == null) { 6 | this.value = [this.getDate(), this.getMonth()+1, this.getFullYear()].join('.'); 7 | } else { 8 | this.value = [day, month, year].join('.'); 9 | } 10 | } 11 | 12 | equals(anotherDate) { 13 | return this.getValue() == anotherDate.getValue(); 14 | } 15 | 16 | getValue() { 17 | return this.value; 18 | } 19 | 20 | getTime() { 21 | return this.toLocaleTimeString(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/infrastructure/web/views/registerForm.ejs: -------------------------------------------------------------------------------- 1 |

Register

2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 |
15 |
-------------------------------------------------------------------------------- /src/infrastructure/web/views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('head.ejs', {}) %> 4 | 5 |
6 | <% if(username != 0) { %> 7 | 10 | <% } %> 11 | <%- include('nav.ejs', {username: (username != 0) ? username : 0}) %> 12 | 13 | <%- include('loginForm.ejs', {}) %> 14 | 15 | <% if(error) { %> 16 |
<%= (error) ? error : "" %>
17 | <% } %> 18 | <%- include('footer.ejs', {}) %> 19 |
20 | 21 | <%- include('micromodal.ejs', {}) %> 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/infrastructure/persistence/sequelize/SequelizeDetectionRepository.js: -------------------------------------------------------------------------------- 1 | const SequelizeDatabaseConnection = require("../SequelizeDatabaseConnection"); 2 | 3 | const repository = SequelizeDatabaseConnection.repository; 4 | 5 | const DataTypes = SequelizeDatabaseConnection.datatypes; 6 | 7 | exports.Detection = repository.define("detection", { 8 | id: { 9 | primaryKey: true, 10 | type: DataTypes.BIGINT 11 | }, 12 | camera: { 13 | type: DataTypes.INTEGER 14 | }, 15 | objects: DataTypes.TEXT, 16 | date: { 17 | type: DataTypes.DATE, 18 | //allowNull: false, 19 | //defaultValue: Sequelize.NOW 20 | }, 21 | time: DataTypes.TEXT 22 | }); -------------------------------------------------------------------------------- /src/infrastructure/web/views/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('head.ejs', {}) %> 4 | 5 |
6 | <%- include('nav.ejs', {username: (username != 0) ? username : 0}) %> 7 |

SurvAPI - Dashboard

8 | 9 |

It looks like something went wrong.

10 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | **Importance** 23 | Tell me how important this feature is to you and replace this sentence with a number from 1 to 5 from less to most important 24 | -------------------------------------------------------------------------------- /src/infrastructure/web/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('head.ejs', {}) %> 4 | 5 |
6 | <%- include('nav.ejs', {username: (username != 0) ? username : 0}) %> 7 |

SurvAPI - Dashboard

8 | 9 | <% if (username == 0 || username == -1) { %> 10 | <% if (username == -1) { %> 11 |
12 | Logged out. 13 |
14 | <% } %> 15 |
16 |
17 | <%- include('registerForm.ejs', {}) %> 18 |
19 |
20 | <%- include('loginForm.ejs', {}) %> 21 |
22 |
23 | <% } else {%> 24 |
25 | Logged in. 26 |
27 | <% } %> 28 | <%- include('footer.ejs', {}) %> 29 |
30 | 31 | <%- include('micromodal.ejs', {}) %> 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | defaults: 7 | run: 8 | working-directory: ./src/ 9 | 10 | on: 11 | push: 12 | branches: [ main ] 13 | pull_request: 14 | branches: [ main ] 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | node-version: [10.x, 12.x, 14.x, 15.x] 24 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v1 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | - run: npm run build --if-present 33 | # - run: npm test 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | ## **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | ## **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | ## **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | ## **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | ## **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/domain/model/services/AuthenticationService.ts: -------------------------------------------------------------------------------- 1 | // bcrypt_ 2 | let bcrypt_ = require("./UserService.js").bcrypt(); 3 | let saltRounds_ = require("./UserService.js").saltRounds(); 4 | import {Req, Response, Session} from "@tsed/common"; 5 | import "@tsed/platform-express"; 6 | 7 | /** 8 | * 9 | * @param {String} password entered password 10 | * @param {String} userPassword persisted user password 11 | * @param {Response} res Response in which the website will be rendered 12 | * @param {Session} sessionTmp current user session 13 | */ 14 | exports.authenticate = async function(password: String, userPassword: String, res: Response, sessionTmp: Req, username: string) { 15 | 16 | bcrypt_.compare(password, userPassword, function(err: Object, result: String) { 17 | 18 | console.log([password, userPassword].join(',')); 19 | 20 | if(!err && result) { 21 | res.render("login.ejs", {username: username, error: ""}); 22 | } else { 23 | res.render("login.ejs", {username: 0, error: "This password is wrong."}); 24 | sessionTmp.destroy(); 25 | return; 26 | } 27 | })}; -------------------------------------------------------------------------------- /src/infrastructure/web/router/controllers/RegistrationController.js: -------------------------------------------------------------------------------- 1 | const SessionManager = require("../../../security/SessionManager"); 2 | const UserService = require("../../../../domain/model/services/UserService"); 3 | const AuthenticationService = require("../../../../domain/model/services/AuthenticationService"); 4 | 5 | // bcrypt 6 | const bcrypt = require('bcrypt'); 7 | const saltRounds = 10; 8 | 9 | exports.postRegister = SessionManager.asyncMiddleware(async (req, res, next) => { 10 | 11 | sessionTmp = req.session; 12 | const { username, password } = req.body; 13 | 14 | sessionTmp.username = username; 15 | 16 | let user = await UserService.findUser(username); 17 | 18 | if(!user) { 19 | UserService.generateHashedPasswordAndCreateUser(username, password, saltRounds); 20 | } 21 | 22 | console.log(sessionTmp.username); 23 | 24 | res.render("index.ejs", {username: sessionTmp.username}); 25 | }); 26 | 27 | exports.getRegister = function(req, res) { 28 | 29 | if(SessionManager.checkSession(req)) res.redirect("/"); 30 | res.render("register.ejs", {username: "", error: ""}); 31 | } -------------------------------------------------------------------------------- /src/db/entrypoint/schema.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS `SurvAPI`; 2 | CREATE DATABASE `SurvAPI`; 3 | USE `SurvAPI` ; 4 | DROP TABLE IF EXISTS `detections`; 5 | CREATE TABLE `detections` ( 6 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 7 | `objects` varchar(255) DEFAULT NULL, 8 | `date` date DEFAULT NULL, 9 | PRIMARY KEY (`id`) 10 | ); 11 | 12 | DROP TABLE IF EXISTS `SurvAPI`.`cameras`; 13 | CREATE TABLE `SurvAPI`.`cameras` ( 14 | `id` INT NOT NULL AUTO_INCREMENT, 15 | `name` VARCHAR(45) NULL, 16 | `description` VARCHAR(45) NULL, 17 | `ip` VARCHAR(45) NULL, 18 | `port` INT NULL, 19 | `resolution` INT NULL, 20 | PRIMARY KEY (`id`), 21 | UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE); 22 | 23 | DROP TABLE IF EXISTS `SurvAPI`.`users`; 24 | CREATE TABLE `SurvAPI`.`users` ( 25 | `id` INT NOT NULL AUTO_INCREMENT, 26 | `username` VARCHAR(45) NOT NULL, 27 | `role` INT NOT NULL DEFAULT 0, 28 | PRIMARY KEY (`id`), 29 | UNIQUE INDEX `username_UNIQUE` (`username` ASC) VISIBLE, 30 | UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE); 31 | 32 | ALTER TABLE `SurvAPI`.`users` 33 | ADD COLUMN `password` VARCHAR(200) NOT NULL AFTER `username`; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marco Steinke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/domain/model/detection/Detection.js: -------------------------------------------------------------------------------- 1 | module.Detection = class { 2 | 3 | constructor(id, objects) { 4 | this.id = id; 5 | this.objects = objects; 6 | this.date = DetectionDate.now(); 7 | this.dateObject = new DetectionDate(); 8 | } 9 | 10 | getId() { 11 | return this.id; 12 | } 13 | 14 | getDate() { 15 | return this.date; 16 | } 17 | 18 | toString() { 19 | let resultObjectsString = ""; 20 | objects.forEach(object => resultObjectsString += [object.label, "confidence=" + object.confidence.toFixed(2)].join(', ')); 21 | return `id=${this.id}, objects=${resultObjectsString}, date=${this.dateObject.getValue()}`; 22 | } 23 | 24 | getObjectLabels() { 25 | return objects.map(object => object.label).join(','); 26 | } 27 | 28 | getObjectConfidences() { 29 | return objects.map(object => object.confidence.toFixed(2)).join(','); 30 | } 31 | 32 | getDateAsObjectAsString() { 33 | return this.dateObject; 34 | } 35 | 36 | // return true if the detection has a valid ID and actually detected an object. 37 | isValid() { 38 | return this.id != -1 && this.objects.length > 0; 39 | } 40 | } -------------------------------------------------------------------------------- /src/infrastructure/web/router/controllers/LoginController.js: -------------------------------------------------------------------------------- 1 | const SessionManager = require("../../../security/SessionManager"); 2 | const UserService = require("../../../../domain/model/services/UserService"); 3 | const AuthenticationService = require("../../../../domain/model/services/AuthenticationService"); 4 | 5 | exports.getLogin = function(req, res) { 6 | 7 | if(SessionManager.checkSession(req)) res.redirect("/"); 8 | res.render("login.ejs", {username: "", error: ""}); 9 | } 10 | 11 | exports.postLogin = SessionManager.asyncMiddleware(async (req, res, next) => { 12 | 13 | sessionTmp = req.session; 14 | const { username, password } = req.body; 15 | 16 | sessionTmp.username = username; 17 | 18 | let user = await UserService.findUser(username); 19 | 20 | console.log(user); 21 | 22 | // if the username is unknown, set the username to 0 and add an error message 23 | if(user == null) { 24 | res.render("login.ejs", {username: 0, error: "This username is unknown."}); 25 | return; 26 | } 27 | 28 | // if the username is known, but the password is wrong, set the username to 0 and add an error message 29 | let loggedIn = await AuthenticationService.authenticate(password, user.password, res, sessionTmp, sessionTmp.username); 30 | 31 | }) -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "surveillance-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "SurvAPIApplication.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "devStart": "nodemon SurvAPIApplication.js", 9 | "deploy": "npx tsc --project ./" 10 | }, 11 | "author": "Marco Steinke", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@tsed/common": "^6.60.1", 15 | "@tsed/core": "^6.60.1", 16 | "@tsed/di": "^6.60.1", 17 | "@tsed/exceptions": "^6.60.1", 18 | "@tsed/json-mapper": "^6.60.1", 19 | "@tsed/platform-express": "^6.60.1", 20 | "@tsed/schema": "^6.60.1", 21 | "@types/multer": "^1.4.7", 22 | "async-middleware": "^1.2.1", 23 | "axios": "^0.21.1", 24 | "bcrypt": "^5.0.1", 25 | "body-parser": "^1.19.0", 26 | "cors": "^2.8.5", 27 | "ejs": "^3.1.6", 28 | "express": "^4.17.1", 29 | "express-session": "^1.17.1", 30 | "mime": "^2.5.2", 31 | "multer": "^1.4.2", 32 | "multipart": "^0.1.5", 33 | "mysql2": "^2.2.5", 34 | "nodemon": "^2.0.12", 35 | "sequelize": "^6.5.0" 36 | }, 37 | "devDependencies": { 38 | "@types/express": "^4.17.13", 39 | "@types/express-session": "^1.17.4", 40 | "@types/node": "^16.4.0", 41 | "ts-node": "^10.1.0", 42 | "typescript": "^4.3.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/infrastructure/persistence/SequelizeDatabaseConnection.js: -------------------------------------------------------------------------------- 1 | // Administrator settings ( will be moved to properties file later ) 2 | exports.port = 3000; 3 | const DB = 'SurvAPI'; 4 | const DB_USER = 'root'; 5 | const DB_PASSWORD = 'example'; 6 | const DB_HOST = 'localhost'; 7 | const testing = true; 8 | 9 | const { Sequelize, DataTypes } = require('sequelize'); 10 | 11 | // Option 2: Passing parameters separately (other dialects) 12 | exports.datatypes = DataTypes; 13 | 14 | const repository = new Sequelize(DB, DB_USER, DB_PASSWORD, { 15 | host: DB_HOST, 16 | dialect: 'mysql', 17 | define: { 18 | timestamps: false 19 | } 20 | }); 21 | 22 | exports.repository = repository; 23 | 24 | /* Checks the database connection each time the application is run. 25 | * Uses Sequelize's "authenticate()" to do so. 26 | */ 27 | exports.checkDatabaseConnection = async function() { 28 | try { 29 | 30 | await repository.authenticate(); 31 | console.log('Connection has been established successfully.'); 32 | console.log(new Date().toUTCString()); 33 | 34 | } catch (error) { 35 | 36 | console.error('Unable to connect to the database:', error); 37 | 38 | } 39 | } 40 | 41 | if(testing) { 42 | const DummyData = require("../../test/DummyData"); 43 | 44 | new DummyData().cameras(); 45 | } -------------------------------------------------------------------------------- /src/infrastructure/web/views/micromodal.ejs: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /src/domain/model/frontend/CameraListRenderer.js: -------------------------------------------------------------------------------- 1 | class CameraListRenderer { 2 | 3 | constructor(cameraRepository) { 4 | this.cameraRepository = cameraRepository; 5 | 6 | } 7 | 8 | static renderCameraList(cameras) { 9 | cameras.forEach( 10 | camera => document.querySelector("#cameras").insertAdjacentHTML( 11 | "beforeend", 12 | `
\ 13 |
\ 14 |
\ 15 |
${camera.name}
\ 16 |

${[camera.getIp(), camera.getPort()].join(':')}

\ 17 |

${camera.description}

\ 18 |
\ 19 |
\ 20 |
` 21 | ) 22 | ) 23 | } 24 | } 25 | 26 | const cameras = [ 27 | new Camera(0, "Front Door", "Camera in the left corner.", CameraConfiguration.createIPCamera("1.1.1.1", 42, 480)), 28 | new Camera(1, "Front Door 2", "Camera in the right corner.", CameraConfiguration.createIPCamera("1.1.1.2", 42, 480)), 29 | new Camera(2, "Hallway", "In front of the cafeteria", CameraConfiguration.createIPCamera("1.1.1.3", 42, 360)), 30 | ]; 31 | 32 | CameraListRenderer.renderCameraList(cameras); -------------------------------------------------------------------------------- /src/domain/model/services/UserService.ts: -------------------------------------------------------------------------------- 1 | const UserRepository = require("../../../infrastructure/persistence/sequelize/SequelizeUserRepository"); 2 | const User = UserRepository.User; 3 | 4 | // bcrypt 5 | const bcrypt = require('bcrypt'); 6 | const saltRounds = 10; 7 | 8 | exports.bcrypt = () => bcrypt; 9 | exports.saltRounds = () => saltRounds; 10 | 11 | exports.findUser = async function(username: String) { 12 | let user = await User.findOne({where: { username: username}}); 13 | 14 | return user; 15 | } 16 | 17 | async function createUser(username: String, hash: String) { 18 | User.create( 19 | { 20 | username: username, 21 | password: hash 22 | } 23 | ); 24 | } 25 | 26 | exports.createUser = createUser; 27 | 28 | /** 29 | * Hash the received password and create a new user with both the username and the hashed password. 30 | * @param {String} username The username received from any form 31 | * @param {String} password The rawPassword received from any form 32 | * @param {Integer} saltRounds Amount of hashes applied to the password 33 | */ 34 | exports.generateHashedPasswordAndCreateUser = function(username: string, password: String, saltRounds: Number) { 35 | 36 | bcrypt.genSalt(saltRounds, function(err: Object, salt: String) { 37 | 38 | bcrypt.hash(password, salt, function(err: Object, hash: String) { 39 | 40 | createUser(username, hash); 41 | }); 42 | }); 43 | } -------------------------------------------------------------------------------- /img/README.adoc: -------------------------------------------------------------------------------- 1 | # 📷 survAPI Surveillance Camera Analysis 2 | 3 | ## Screenshots: 4 | 5 | image::https://github.com/MarcoSteinke/Security-Cam/blob/main/img/screenshot.png?raw=true[width=800] 6 | 7 | image::https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/main/img/addcam1.PNG[width=800] 8 | 9 | image::https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/main/img/ui.png[width=800] 10 | 11 | image::https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/main/img/start.PNG[width=800] 12 | 13 | image::https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/main/img/add.PNG[width=800] 14 | 15 | 16 | ### 3. Security: 17 | 18 | This application is secured using `express-session` and `bcrypt`. This way it is save against the most common attacks and your security cameras will not be more attackable 19 | by deploying this application. The `registration form` and `login form` are located in separate ejs views for simplified modification. 20 | 21 | image::https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/main/img/landing.PNG[width=800] 22 | 23 | ### 4. Persistence: 24 | 25 | As you can see in the following screenshot, user passwords will be secured using bcrypt. You also have a role system which uses `integers` in the database, 26 | but in the backend you can simply map them to `strings`, such as `ADMIN`, `DEV`, `USER`. 27 | 28 | image::https://raw.githubusercontent.com/MarcoSteinke/survAPI-Surveillance-Camera-Analysis/main/img/db.PNG[width=800] 29 | -------------------------------------------------------------------------------- /src/infrastructure/web/views/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | survAPI - Surveillance | Analysis 5 | 6 | 7 | 8 | 9 | 10 | 11 | 25 | 26 | 32 | 33 | -------------------------------------------------------------------------------- /src/domain/model/frontend/LatestDetectionRenderer.js: -------------------------------------------------------------------------------- 1 | /* This class manages the rendering of latest detections. If you want to change the way they are rendered, you have 2 | * to change this class. 3 | */ 4 | class LatestDetectionRenderer { 5 | 6 | static DISPLAY_COUNT = 10; 7 | static LATEST_DETECTIONS = new Map(); 8 | 9 | // Standard method to show latest detections, where the parameter is given by the application. 10 | static showLatestDetections(latestDetections) { 11 | 12 | latestDetections.forEach(detection => LatestDetectionRenderer.LATEST_DETECTIONS.set(detection.getId(), detection)); 13 | 14 | document.querySelectorAll(".detection").forEach(detection => detection.remove()); 15 | const LATEST_DETECTION_ANCHOR = document.querySelector("#latest"); 16 | 17 | latestDetections.forEach( 18 | detection => LATEST_DETECTION_ANCHOR.insertAdjacentHTML("beforeend", 19 | `\ 20 | ${detection.getId()}\ 21 | ${detection.getObjectLabels()}\ 22 | ${detection.getObjectConfidences()}\ 23 | ${detection.dateObject.getValue()}\ 24 | ${detection.dateObject.getTime()}\ 25 | `) 26 | ); 27 | } 28 | 29 | static details(id) { 30 | let detection = LatestDetectionRenderer.LATEST_DETECTIONS.get(id); 31 | document.querySelector("#latestDetectionModal-title").innerHTML = "Detection " + detection.getId(); 32 | document.querySelector("#latestDetectionModal-content").innerHTML = detection.toString().replaceAll(",", "
"); 33 | MicroModal.show('latestDetectionModal'); 34 | } 35 | } -------------------------------------------------------------------------------- /src/test/DummyData.js: -------------------------------------------------------------------------------- 1 | module.exports = class DummyData { 2 | 3 | constructor(camera) { 4 | this.camera = camera; 5 | this.cameraRepository = require("../infrastructure/persistence/sequelize/SequelizeCameraRepository"); 6 | this.Camera = this.cameraRepository.Camera; 7 | this.userRepository = require("../infrastructure/persistence/sequelize/SequelizeUserRepository"); 8 | this.detectionRepository = require("../infrastructure/persistence/sequelize/SequelizeDetectionRepository"); 9 | } 10 | 11 | async cameras() { 12 | 13 | await this.Camera.create( 14 | { 15 | name: "Front Door", 16 | description: "Simple Front door camera", 17 | ip: "192.168.0.2", 18 | port: 37482, 19 | resolution: 720 20 | } 21 | ); 22 | 23 | await this.Camera.create( 24 | { 25 | name: "Kitchen", 26 | description: "If you want to know who steals your chocolate", 27 | ip: "192.168.0.2", 28 | port: 37482, 29 | resolution: 360 // bad resolution so actually you do not know who stole it :) 30 | } 31 | ); 32 | 33 | await this.Camera.create( 34 | { 35 | name: "Level 1 Elevator", 36 | description: "Camera directed at the doors in level 1", 37 | ip: "192.168.1.55", 38 | port: 37482, 39 | resolution: 480 40 | } 41 | ); 42 | 43 | await this.Camera.create( 44 | { 45 | name: "Level 2 Elevator", 46 | description: "Camera directed at the doors in level 2", 47 | ip: "192.168.2.55", 48 | port: 37482, 49 | resolution: 480 50 | } 51 | ); 52 | } 53 | } -------------------------------------------------------------------------------- /src/infrastructure/web/views/camera.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('head.ejs', {}) %> 4 | 5 |
6 | <%- include('nav.ejs', {username: username}) %> 7 |
8 |
9 |

Current camera: <%= cameraId %>

10 |
11 |
12 |

Latest Detections:

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
ID:Objects:Confidences:Date:Time:
26 |
27 |
28 |
29 |
30 |
31 |

More Cameras:

32 |
33 |
34 |
35 |
36 |
37 | <%- include('footer.ejs', {}) %> 38 |
39 | 40 | <%- include('micromodal.ejs', {}) %> 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/infrastructure/web/static/style.css: -------------------------------------------------------------------------------- 1 | #state { 2 | height: 500px; 3 | text-align: center; margin: 0px !important; 4 | } 5 | 6 | #cameras>*, #cameras>*>* { 7 | margin: 6px; 8 | padding: 0; 9 | } 10 | 11 | .other-camera { 12 | } 13 | 14 | .col { 15 | margin: 24px; 16 | text-align: center; 17 | } 18 | 19 | .btn { 20 | margin: 12px; 21 | padding-left: 48px !important; 22 | padding-right: 48px !important; 23 | border-radius: 0px !important; 24 | } 25 | 26 | input { 27 | border-radius: 0px !important; 28 | } 29 | 30 | input:hover { 31 | border: 1px solid black !important; 32 | } 33 | 34 | nav, body, .container, .row { 35 | box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.9) !important;; 36 | } 37 | 38 | .btn:hover { 39 | -webkit-box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.75) !important; 40 | -moz-box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.75) !important; 41 | box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.75) !important; 42 | cursor: pointer; 43 | } 44 | 45 | .col { 46 | padding: 0; 47 | } 48 | 49 | tr:nth-child(odd) { 50 | background-color: white; 51 | 52 | } 53 | 54 | :is(thead) tr { 55 | background-color: #f1f1f1; 56 | } 57 | 58 | tr:nth-child(even) { 59 | background: rgb(230,230,230); 60 | 61 | } 62 | 63 | tr:hover, tr:nth-child(even):hover, tr:nth-child(odd):hover { 64 | background-color: aliceblue; 65 | } 66 | 67 | tr>*:hover, table:hover, tr>*>*:hover { 68 | cursor: pointer; 69 | } 70 | 71 | body { 72 | background-color: #f1f1f1; text-align: center; 73 | } 74 | 75 | .other-camera:hover { 76 | -webkit-box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.75); 77 | -moz-box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.75); 78 | box-shadow: 1px 0px 39px -23px rgba(0,0,0,0.75); 79 | cursor: pointer; 80 | } 81 | 82 | .modal { 83 | display: none; 84 | } 85 | 86 | .modal.is-open { 87 | display: block; 88 | } 89 | 90 | .card { 91 | border-radius: 0 !important; 92 | } -------------------------------------------------------------------------------- /src/domain/model/mockDatabase/MockDatabase.ts: -------------------------------------------------------------------------------- 1 | let Detection = require("../detection/Detection.js"); 2 | type Detection = typeof Detection; 3 | 4 | class MockDatabase { 5 | 6 | db: Array; 7 | lastDetection: number; 8 | intervalDuration: Number; 9 | 10 | constructor() { 11 | this.db = []; 12 | this.lastDetection = Date.now(); 13 | this.intervalDuration = 10; 14 | } 15 | 16 | saveDetection(detection: Detection) { 17 | 18 | if(detection.getDate() / 1000 - this.lastDetection / 1000 < this.intervalDuration) { 19 | console.log("too short"); 20 | return; 21 | } 22 | 23 | if(detection != null) { 24 | 25 | let alreadySaved = false; 26 | let interrupted = false; 27 | 28 | this.db.forEach(savedDetection => { 29 | 30 | alreadySaved = alreadySaved || savedDetection.getId() == detection.getId(); 31 | 32 | if(alreadySaved) { 33 | 34 | console.log("There was already a detection with ID " + detection.getId()); 35 | interrupted = true; 36 | } 37 | }); 38 | 39 | if(!interrupted) { 40 | this.db.push(detection); 41 | this.lastDetection = Date.now(); 42 | } 43 | 44 | } 45 | } 46 | 47 | getDetectionById(detectionId: Number) { 48 | let detection: Detection; 49 | 50 | this.db.forEach(storedDetection => { 51 | 52 | if(storedDetection.getId() == detectionId) { 53 | detection = storedDetection; 54 | } 55 | }); 56 | 57 | return (detection != null) ? detection : new Detection(-1, null); 58 | } 59 | 60 | getDetectionsByDate(date: Date) { 61 | let results: Array = []; 62 | 63 | this.db.forEach(detection => { 64 | if(detection.dateObject.equals(date)) { 65 | results.push(detection); 66 | } 67 | }); 68 | 69 | return results; 70 | } 71 | } 72 | 73 | const DATABASE: MockDatabase = new MockDatabase(); -------------------------------------------------------------------------------- /src/infrastructure/web/views/nav.ejs: -------------------------------------------------------------------------------- 1 | 47 | -------------------------------------------------------------------------------- /img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Build 10 | build/* 11 | build 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | assets 30 | assets/* 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (https://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | jspm_packages/ 50 | 51 | # Snowpack dependency directory (https://snowpack.dev/) 52 | web_modules/ 53 | 54 | # TypeScript cache 55 | *.tsbuildinfo 56 | 57 | # Optional npm cache directory 58 | .npm 59 | 60 | # Optional eslint cache 61 | .eslintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variables file 79 | .env 80 | .env.test 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | # TernJS port file 113 | .tern-port 114 | 115 | # Stores VSCode versions used for testing VSCode extensions 116 | .vscode-test 117 | 118 | # yarn v2 119 | .yarn/cache 120 | .yarn/unplugged 121 | .yarn/build-state.yml 122 | .yarn/install-state.gz 123 | .pnp.* -------------------------------------------------------------------------------- /src/infrastructure/web/static/micromodal.css: -------------------------------------------------------------------------------- 1 | /**************************\ 2 | Basic Modal Styles 3 | \**************************/ 4 | 5 | .modal { 6 | font-family: -apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif; 7 | } 8 | 9 | .modal__overlay { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | right: 0; 14 | bottom: 0; 15 | background: rgba(0,0,0,0.6); 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | } 20 | 21 | .modal__container { 22 | background-color: #fff; 23 | padding: 30px; 24 | max-width: 500px; 25 | max-height: 100vh; 26 | border-radius: 4px; 27 | overflow-y: auto; 28 | box-sizing: border-box; 29 | } 30 | 31 | .modal__header { 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | } 36 | 37 | .modal__title { 38 | margin-top: 0; 39 | margin-bottom: 0; 40 | font-weight: 600; 41 | font-size: 1.25rem; 42 | line-height: 1.25; 43 | color: #00449e; 44 | box-sizing: border-box; 45 | } 46 | 47 | .modal__close { 48 | background: transparent; 49 | border: 0; 50 | } 51 | 52 | .modal__header .modal__close:before { content: "\2715"; } 53 | 54 | .modal__content { 55 | margin-top: 2rem; 56 | margin-bottom: 2rem; 57 | line-height: 1.5; 58 | color: rgba(0,0,0,.8); 59 | } 60 | 61 | .modal__btn { 62 | font-size: .875rem; 63 | padding-left: 1rem; 64 | padding-right: 1rem; 65 | padding-top: .5rem; 66 | padding-bottom: .5rem; 67 | background-color: #e6e6e6; 68 | color: rgba(0,0,0,.8); 69 | border-radius: .25rem; 70 | border-style: none; 71 | border-width: 0; 72 | cursor: pointer; 73 | -webkit-appearance: button; 74 | text-transform: none; 75 | overflow: visible; 76 | line-height: 1.15; 77 | margin: 0; 78 | will-change: transform; 79 | -moz-osx-font-smoothing: grayscale; 80 | -webkit-backface-visibility: hidden; 81 | backface-visibility: hidden; 82 | -webkit-transform: translateZ(0); 83 | transform: translateZ(0); 84 | transition: -webkit-transform .25s ease-out; 85 | transition: transform .25s ease-out; 86 | transition: transform .25s ease-out,-webkit-transform .25s ease-out; 87 | } 88 | 89 | .modal__btn:focus, .modal__btn:hover { 90 | -webkit-transform: scale(1.05); 91 | transform: scale(1.05); 92 | } 93 | 94 | .modal__btn-primary { 95 | background-color: #00449e; 96 | color: #fff; 97 | } 98 | 99 | 100 | 101 | /**************************\ 102 | Demo Animation Style 103 | \**************************/ 104 | @keyframes mmfadeIn { 105 | from { opacity: 0; } 106 | to { opacity: 1; } 107 | } 108 | 109 | @keyframes mmfadeOut { 110 | from { opacity: 1; } 111 | to { opacity: 0; } 112 | } 113 | 114 | @keyframes mmslideIn { 115 | from { transform: translateY(15%); } 116 | to { transform: translateY(0); } 117 | } 118 | 119 | @keyframes mmslideOut { 120 | from { transform: translateY(0); } 121 | to { transform: translateY(-10%); } 122 | } 123 | 124 | .micromodal-slide { 125 | display: none; 126 | } 127 | 128 | .micromodal-slide.is-open { 129 | display: block; 130 | } 131 | 132 | .micromodal-slide[aria-hidden="false"] .modal__overlay { 133 | animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1); 134 | } 135 | 136 | .micromodal-slide[aria-hidden="false"] .modal__container { 137 | animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1); 138 | } 139 | 140 | .micromodal-slide[aria-hidden="true"] .modal__overlay { 141 | animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1); 142 | } 143 | 144 | .micromodal-slide[aria-hidden="true"] .modal__container { 145 | animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1); 146 | } 147 | 148 | .micromodal-slide .modal__container, 149 | .micromodal-slide .modal__overlay { 150 | will-change: transform; 151 | } 152 | 153 | .modal__container { 154 | width: 400px; 155 | } -------------------------------------------------------------------------------- /src/infrastructure/web/views/addCamera.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('head.ejs', {}) %> 4 | 27 | 28 |
29 | <%- include('nav.ejs', {username: username}) %> 30 | 31 |
32 |
33 |

Add Camera:

34 |
35 |
36 | 37 | 47 |
48 |
49 | 50 | 61 |
62 |
63 | 64 | 74 |
75 |
76 | 77 | 85 |
86 |
87 | 88 | 94 |
95 | 96 |
97 | 98 |
99 |

Information:

100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
NameValue
Number of Cameras<%= cameras.length %>
116 | 117 |
118 |
119 |

Your cameras:

120 |
121 | <% cameras.forEach(function(camera) { %> 122 |
123 |
124 |
125 | 126 |
127 |
<%= camera.name %>
128 |

<%= camera.description %>

129 |

<%= camera.ip %>

130 |

<%= camera.port %>

131 |
132 |
133 | <% }); %> 134 |
135 |
136 |
137 | 138 | <%- include('footer.ejs', {}) %> 139 |
140 | 141 | <%- include('micromodal.ejs', {}) %> 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/SecurityCam.js: -------------------------------------------------------------------------------- 1 | /* * * * * * * * * * * * * * * * * **/ 2 | /* required variables DO NOT CHANGE */ 3 | let video; 4 | let detector; 5 | let objects = []; 6 | let latestDetections = []; 7 | 8 | /* * * * * * * * * * * * * * * * * **/ 9 | 10 | /* user oriented variables CHANGE */ 11 | // fill targets with labels to detect and store them 12 | // or leave it empty to detect and store everything. 13 | let targets = []; 14 | DATABASE.intervalDuration = 3; 15 | const LATEST_DETECTION_DISPLAY_COUNT = 10; 16 | 17 | /* * * * * * * * * * * * * * * * * **/ 18 | 19 | // prepare the setup of camera and ml5 Object Detection 20 | function preload() { 21 | video = createCapture(VIDEO); 22 | video.hide(); 23 | detector = ml5.objectDetector('cocossd', modelLoaded); 24 | } 25 | 26 | // a simple method to calculate the time between two timestamps 27 | function timeBetweenInSeconds(date, anotherDate) { 28 | return Math.abs(date / 1000 - anotherDate / 1000); 29 | } 30 | 31 | // update the header if the model is loaded 32 | function modelLoaded() { 33 | //document.querySelector("#state").className = "loaded"; 34 | //document.querySelector("#state").innerHTML = "Model loaded."; 35 | console.log("model loaded"); 36 | } 37 | 38 | // setup the canvas and display the video 39 | function setup() { 40 | createCanvas(800, 800); 41 | image(video, 0, 0); 42 | let defaultCam = document.querySelector("canvas"); 43 | defaultCam.remove(); 44 | document.querySelector("#state").appendChild(defaultCam); 45 | defaultCam = document.querySelector("canvas"); 46 | defaultCam.style.margin = "auto"; 47 | 48 | } 49 | 50 | function collectObjectsByTargets(objectCollection) { 51 | 52 | return objects.filter(object => targets.includes(object.label)) 53 | .map(targetedObject => result.push(targetedObject)); 54 | } 55 | 56 | // try to detect any objects in the canvas 57 | function detect() { 58 | 59 | // ml5 detect method returns error and result object 60 | detector.detect(video, (error, result) => { 61 | objects = result; 62 | }); 63 | 64 | // if the predefined interval of seconds has passed and there are any objects, store them. 65 | if(timeBetweenInSeconds(Date.now(), DATABASE.lastDetection) > DATABASE.intervalDuration) { 66 | 67 | // If there are certain targets defined, only detect them 68 | if(targets.length > 0) { 69 | 70 | DATABASE.saveDetection(new Detection( 71 | DATABASE.db.length + 1, 72 | collectObjectsByTargets(objects) 73 | )); 74 | 75 | } else { 76 | 77 | DATABASE.saveDetection(new Detection( 78 | DATABASE.db.length + 1, 79 | objects 80 | )); 81 | } 82 | 83 | // Only display a predefined amount of detections. 84 | if(latestDetections.length > LatestDetectionRenderer.DISPLAY_COUNT) { 85 | 86 | // remove the first entry 87 | latestDetections.shift(); 88 | 89 | } 90 | 91 | 92 | latestDetections.push( 93 | 94 | new Detection( 95 | DATABASE.db.length, 96 | collectObjectsByTargets(objects) 97 | ) 98 | ); 99 | 100 | LatestDetectionRenderer.showLatestDetections(latestDetections); 101 | 102 | } 103 | } 104 | 105 | // Simply check if the current label is defined as a target. 106 | function isTarget(label) { 107 | 108 | return targets.filter(target => target == label).length > 0; 109 | } 110 | 111 | // method to label and mark all detections 112 | function label() { 113 | if(objects && objects.length > 0) { 114 | objects.forEach( object => { 115 | 116 | // if the object is from type "TARGET" mark and label it 117 | if(isTarget(object.label) && targets.length > 0) { 118 | text(object.label, object.x, object.y - 10); 119 | 120 | stroke(0, 255, 0); 121 | noFill(); 122 | rect(object.x, object.y, object.width, object.height); 123 | stroke(0,0,0); 124 | 125 | } else if(targets.length == 0) { 126 | text(object.label, object.x, object.y - 10); 127 | 128 | stroke(0, 255, 0); 129 | noFill(); 130 | rect(object.x, object.y, object.width, object.height); 131 | stroke(0,0,0); 132 | } 133 | 134 | }); 135 | } 136 | } 137 | 138 | 139 | // draw function will execute each tick 140 | function draw() { 141 | 142 | clear(); 143 | 144 | image(video, 0, 0); 145 | 146 | detect(); 147 | label(); 148 | 149 | } 150 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Object Detection 7 | 8 | 9 | 10 | 11 | 12 | 26 | 32 | 33 | 34 | 35 |
36 | 42 |
43 |
44 |

Current camera:

45 |
46 |
47 |

Latest Detections:

48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
ID:Objects:Confidences:Date:Time:
61 |
62 |
63 |
64 |
65 |
66 |

More Cameras:

67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./build/", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | "rootDirs": ["*", "*/*"], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/SurvAPIApplication.js: -------------------------------------------------------------------------------- 1 | // Administrator settings ( will be moved to properties file later ) 2 | 3 | // if this is set to true, detections will be deleted on restart 4 | const testing = true; 5 | 6 | const express = require('express'); 7 | const survAPIApplication = express(); 8 | 9 | const SurvAPIRouter = require('./infrastructure/web/router/SurvAPIRouter.js'); 10 | 11 | survAPIApplication.set('views','./infrastructure/web/views'); 12 | 13 | // enable sessions 14 | const session = require('express-session'); 15 | survAPIApplication.use(session({secret: 'ssshhhhh'})); 16 | let sessionTmp; 17 | 18 | /*function checkSession(request) { 19 | sessionTmp = request.session; 20 | 21 | return sessionTmp.username != undefined; 22 | }*/ 23 | 24 | // bcrypt 25 | const bcrypt = require('bcrypt'); 26 | const saltRounds = 10; 27 | 28 | const cors = require('cors'); 29 | 30 | // use ejs as rendering (view) engine 31 | survAPIApplication.set('view-engine', 'ejs'); 32 | survAPIApplication.use(express.static('./infrastructure/web/static')); 33 | var bodyParser = require('body-parser'); 34 | // handle form data 35 | var multer = require('multer'); 36 | var upload = multer(); 37 | 38 | // for parsing application/json 39 | survAPIApplication.use(bodyParser.json()); 40 | 41 | // for parsing application/xwww- 42 | survAPIApplication.use(bodyParser.urlencoded({ extended: true })); 43 | //form-urlencoded 44 | 45 | // for parsing multipart/form-data 46 | survAPIApplication.use(upload.array()); 47 | survAPIApplication.use(express.static('public')); 48 | survAPIApplication.use(express.static('subsystems')); 49 | survAPIApplication.use(express.static(__dirname)); 50 | 51 | survAPIApplication.use(cors()); 52 | 53 | // enable MIME 54 | const mime = require('mime'); 55 | 56 | // fs 57 | const fs = require('fs'); 58 | 59 | mime.getType('txt'); // ⇨ 'text/plain' 60 | mime.getExtension('text/plain'); // ⇨ 'txt' 61 | 62 | mime.getExtension('text/plain'); // ⇨ 'txt' 63 | mime.getExtension('application/json'); // ⇨ 'json' 64 | mime.getExtension('text/html; charset=utf8'); // ⇨ 'html' 65 | mime.getExtension('text/html'); // ⇨ 'html' 66 | 67 | // dummy data 68 | const DummyData = require('./test/DummyData.js'); 69 | const SequelizeCameraRepository = require("./infrastructure/persistence/sequelize/SequelizeCameraRepository"); 70 | const testData = new DummyData(SequelizeCameraRepository.Camera); 71 | 72 | // Module imports: 73 | const IMPORTS_PREFIX = './subsystems'; 74 | 75 | const SessionManager = require("./infrastructure/security/SessionManager"); 76 | 77 | // enable axios for requests 78 | const axios = require('axios'); 79 | 80 | // Require body-parser to parse requests easily 81 | var bodyParser = require('body-parser'); 82 | survAPIApplication.use(express.json()); 83 | 84 | // Controllers 85 | const LogoutController = require('./infrastructure/web/router/controllers/LogoutController'); 86 | const SurvAPIController = require('./infrastructure/web/router/controllers/SurvAPIController'); 87 | const LoginController = require('./infrastructure/web/router/controllers/LoginController'); 88 | const RegistrationController = require('./infrastructure/web/router/controllers/RegistrationController'); 89 | 90 | survAPIApplication.get('/', SurvAPIController.index); 91 | 92 | // GET Route for login 93 | survAPIApplication.get('/login', LoginController.getLogin); 94 | 95 | // POST Route for login 96 | survAPIApplication.post('/login', LoginController.postLogin); 97 | 98 | // LOGOUT 99 | survAPIApplication.get('/logout', LogoutController.logout); 100 | 101 | // GET Route for register 102 | survAPIApplication.get('/register', RegistrationController.getRegister); 103 | 104 | // POST Route for register 105 | // This route is only accessible by the predefined admin user 106 | survAPIApplication.post('/register', RegistrationController.postRegister); 107 | 108 | // Route for testing ejs templates 109 | survAPIApplication.get('/detection', (req, res) => { 110 | if(SessionManager.checkSession(req)) 111 | res.render("form.ejs", {username: sessionTmp.username}); 112 | else res.redirect("/login"); 113 | }); 114 | 115 | survAPIApplication.get('/camera/:id', SessionManager.asyncMiddleware(async (req, res, next) => { 116 | 117 | if(!SessionManager.checkSession(req)) res.render("login.ejs", {error: "Please login before accessing this page.", username: 0}); 118 | 119 | const cameras = await Camera.findAll(); 120 | 121 | const selectedCamera = await Camera.findAll({ 122 | attributes: ["id"], 123 | where: {id: req.params.id} // Your filters here 124 | }); 125 | 126 | console.log(selectedCamera); 127 | 128 | res.render("camera.ejs", {cameraId: req.params.id, cameras: cameras, selectedCamera: selectedCamera, username: sessionTmp.username}); 129 | })); 130 | 131 | survAPIApplication.get('/cameras', SessionManager.asyncMiddleware(async (req, res, next) => { 132 | 133 | if(!SessionManager.checkSession(req)) res.render("login.ejs", {error: "Please login before accessing this page.", username: 0}); 134 | 135 | const Camera = require("./infrastructure/persistence/sequelize/SequelizeCameraRepository").Camera; 136 | const cameras = await Camera.findAll(); 137 | console.log(cameras); 138 | res.render("addCamera.ejs", {cameras: cameras, username: (sessionTmp = req.session).username}); 139 | })); 140 | 141 | survAPIApplication.post('/cameras/add', SessionManager.asyncMiddleware(async (req, res, next) => { 142 | 143 | if(!SessionManager.checkSession(req)) res.render("login.ejs", {error: "Please login before accessing this page.", username: 0}); 144 | 145 | // TODO validation 146 | const { name, description, ip, port, resolution} = req.body; 147 | 148 | const camera = await Camera.create( 149 | { 150 | name: name, 151 | description: description, 152 | ip: ip, 153 | port: port, 154 | resolution: resolution 155 | } 156 | ); 157 | 158 | res.redirect('/cameras/success', {username: sessionTmp.username}); 159 | }) 160 | ); 161 | 162 | survAPIApplication.get('/cameras/success', SessionManager.asyncMiddleware(async (req, res, next) => { 163 | 164 | if(!SessionManager.checkSession(req)) res.render("login.ejs", {error: "Please login before accessing this page.", username: 0}); 165 | 166 | const cameras = await Camera.findAll(); 167 | console.log(cameras); 168 | res.render("addCamera.ejs", {cameras: cameras, success: true, username: sessionTmp.username}); 169 | })); 170 | 171 | // Route used to persist detections inside of the database. Data sent to the server will be validated by Sequelize. 172 | survAPIApplication.post('/detection', SessionManager.asyncMiddleware(async (req, res, next) => { 173 | 174 | // parse fields from body 175 | const { camera, objects } = req.body; 176 | 177 | // persist as detection 178 | const detection = await Detection.create( 179 | { 180 | camera: camera, 181 | objects: objects, 182 | date: new Date(), 183 | time: new Date().toString().split(new Date().getFullYear())[1].split("GMT")[0].trim() 184 | } 185 | ); 186 | }) 187 | ); 188 | 189 | // Thanks @https://betterprogramming.pub/video-stream-with-node-js-and-html5-320b3191a6b6 190 | survAPIApplication.get('/video', function(req, res) { 191 | 192 | // place any video to test this streaming route. 193 | const path = 'assets/video.mp4' 194 | const stat = fs.statSync(path) 195 | const fileSize = stat.size 196 | const range = req.headers.range 197 | 198 | if (range) { 199 | 200 | const parts = range.replace(/bytes=/, "").split("-") 201 | const start = parseInt(parts[0], 10) 202 | 203 | const end = parts[1] 204 | ? parseInt(parts[1], 10) 205 | : fileSize-1 206 | 207 | const chunksize = (end-start)+1 208 | const file = fs.createReadStream(path, {start, end}) 209 | // Define header for the packages sent to the browser 210 | const head = { 211 | 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 212 | 'Accept-Ranges': 'bytes', 213 | 'Content-Length': chunksize, 214 | 'Content-Type': 'video/mp4', 215 | } 216 | 217 | res.writeHead(206, head); 218 | file.pipe(res); 219 | 220 | } else { 221 | 222 | const head = { 223 | 'Content-Length': fileSize, 224 | 'Content-Type': 'video/mp4', 225 | } 226 | 227 | res.writeHead(200, head) 228 | fs.createReadStream(path).pipe(res) 229 | } 230 | }); 231 | 232 | // Error mapping 233 | survAPIApplication.get('*', SurvAPIRouter.error); 234 | 235 | const SequelizeDatabaseConnection = require("./infrastructure/persistence/SequelizeDatabaseConnection"); 236 | 237 | survAPIApplication.listen(SequelizeDatabaseConnection.port, () => SequelizeDatabaseConnection.checkDatabaseConnection()); 238 | -------------------------------------------------------------------------------- /src/infrastructure/web/static/require.js: -------------------------------------------------------------------------------- 1 | /** vim: et:ts=4:sw=4:sts=4 2 | * @license RequireJS 2.3.6 Copyright jQuery Foundation and other contributors. 3 | * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE 4 | */ 5 | var requirejs,require,define;!function(global,setTimeout){var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.6",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){var i;if(e)for(i=0;i