├── 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 |
2 |
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
11 |
15 |
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 |
6 |
7 |
13 |
21 |
22 |
23 | Try hitting the tab key and notice how the focus stays
24 | within the modal itself. Also, esc to close modal.
25 |
26 |
27 |
37 |
38 |
39 |
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 | ID:
17 | Objects:
18 | Confidences:
19 | Date:
20 | Time:
21 |
22 |
23 |
24 |
25 |
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 |
48 |
64 |
--------------------------------------------------------------------------------
/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 |
97 |
98 |
99 |
Information:
100 |
101 |
102 |
103 |
104 |
105 | Name
106 | Value
107 |
108 |
109 |
110 |
111 | Number of Cameras
112 | <%= cameras.length %>
113 |
114 |
115 |
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 |
37 |
41 |
42 |
43 |
44 |
Current camera:
45 |
46 |
47 |
Latest Detections:
48 |
49 |
50 |
51 | ID:
52 | Objects:
53 | Confidences:
54 | Date:
55 | Time:
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
More Cameras:
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
85 | Try hitting the tab key and notice how the focus stays within the modal itself. Also, esc to close modal.
86 |
87 |
88 |
92 |
93 |
94 |
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