You can enable or disable push notifications for each camera. If enabled, a push notification will be
23 | sent as soon as a new image is received via FTP.
24 |
25 |
26 |
27 |
28 |
29 |
{{ setting.cameraName }}
30 |
31 |
32 | ON
36 | OFF
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
104 |
--------------------------------------------------------------------------------
/client/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import Login from '@/pages/Login';
4 | import Recordings from '@/pages/Recordings';
5 | import RecordingsDetail from '@/pages/RecordingsDetail';
6 | import Liveview from '@/pages/Liveview';
7 | import LiveviewDetail from '@/pages/LiveviewDetail';
8 | import Livestream from '@/pages/Livestream';
9 | import LivestreamDetail from '@/pages/LivestreamDetail';
10 | import Settings from '@/pages/Settings';
11 | import NotFound from '@/pages/NotFound';
12 |
13 | Vue.use(Router);
14 |
15 | export default new Router({
16 | base: process.env.APP_BASE_URL,
17 | mode: 'history',
18 | routes: [
19 | {
20 | path: '/',
21 | name: 'root',
22 | redirect: '/liveview',
23 | },
24 | {
25 | path: '/login',
26 | name: 'login',
27 | component: Login,
28 | },
29 | {
30 | path: '/recordings',
31 | name: 'recordings',
32 | component: Recordings,
33 | },
34 | {
35 | path: '/recordings/:id',
36 | name: 'recordings-detail',
37 | component: RecordingsDetail,
38 | },
39 | {
40 | path: '/liveview',
41 | name: 'liveview',
42 | component: Liveview,
43 | },
44 | {
45 | path: '/liveview/:id',
46 | name: 'liveview-detail',
47 | component: LiveviewDetail,
48 | },
49 | {
50 | path: '/livestream',
51 | name: 'livestream',
52 | component: Livestream,
53 | },
54 | {
55 | path: '/livestream/:id',
56 | name: 'livestream-detail',
57 | component: LivestreamDetail,
58 | },
59 | {
60 | path: '/settings',
61 | name: 'settings',
62 | component: Settings,
63 | },
64 | {
65 | path: '*',
66 | name: 'not-found',
67 | component: NotFound,
68 | },
69 | ],
70 | });
71 |
--------------------------------------------------------------------------------
/client/src/services/api.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import router from '../router';
3 |
4 | export default function () {
5 | const axiosClient = axios.create({
6 | baseURL: process.env.API_BASE_URL,
7 | });
8 |
9 | axiosClient.interceptors.request.use((config) => {
10 | router.app.$Progress.start();
11 | return config;
12 | }, error => Promise.reject(error));
13 |
14 | axiosClient.interceptors.response.use((response) => {
15 | router.app.$Progress.finish();
16 | return response;
17 | }, (error) => {
18 | router.app.$Progress.fail();
19 | // if we receive an unauthorized response
20 | if (error.response.status === 401) {
21 | // and we are not trying to login
22 | if (!(error.config.method === 'post' && /api\/v\d\/auth/.test(error.config.url))) {
23 | // redirect to login page
24 | router.replace({ name: 'login' });
25 | }
26 | }
27 | return Promise.reject(error);
28 | });
29 |
30 | return axiosClient;
31 | }
32 |
--------------------------------------------------------------------------------
/client/src/utils/urlUtils.js:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | /**
4 | * Append hash fragment with current time to the provided url.
5 | * This will prevent browsers to cache the (image) object.
6 | */
7 | appendHashFragment(url) {
8 | let plainUrl = url;
9 | const hashIndex = url.indexOf('#');
10 | if (hashIndex !== -1) {
11 | plainUrl = url.substring(0, hashIndex);
12 | }
13 | return `${plainUrl}#${new Date().getTime()}`;
14 | },
15 |
16 | };
17 |
--------------------------------------------------------------------------------
/client/static/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/client/static/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/client/static/img/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/client/static/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/client/static/img/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/client/static/img/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/client/static/img/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/client/static/img/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/client/static/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/client/static/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/client/static/img/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/client/static/img/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/client/static/img/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/client/static/img/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Surveillance Center",
3 | "short_name": "Surveillance Center",
4 | "icons": [
5 | {
6 | "src": "/sc/static/img/icons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/sc/static/img/icons/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "start_url": "/sc/",
17 | "display": "standalone",
18 | "background_color": "#343a40",
19 | "theme_color": "#343a40"
20 | }
21 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | ## Configuration
2 |
3 | You can configure Surveillance Center by editing the `application.properties`
4 | file and restart the application afterwards.
5 |
6 | There is an example file [application-example.properties](https://github.com/1element/sc/blob/master/src/main/resources/application-example.properties)
7 | with annotations that shows all configuration options.
8 |
9 | It's not necessary to change all default values, but there a few required ones.
10 |
11 | Most features like push notifications, camera health check or off-site backup
12 | are disabled by default.
13 |
14 | At least the following settings must be changed to reflect your setup:
15 |
16 | ```
17 | ###################
18 | # Global settings #
19 | ###################
20 | # Absolute path to the directory to save images.
21 | sc.image.storage-dir=/home/surveillance/images/
22 |
23 | # The username used to login.
24 | sc.security.username=admin
25 |
26 | # The password used to login. Make sure to change this!
27 | # This must be hashed with BCrypt. The default password below is 'password'.
28 | sc.security.password=$2a$04$xdRJiiGwwHEbSgs6ucM0DOOCVEUQVaKtB3UPO16.h65sCWzPlkFHC
29 |
30 | # Internal secret key used to sign the JWT token.
31 | # Simply change this to something else, you don't have to remember the secret.
32 | sc.security.secret=verySecretKeyChangeMe
33 |
34 | # List of available camera ids (comma separated, don't use any special characters).
35 | # Each camera id listed here must have it's own configuration key (sc.camera[id]), see below.
36 | sc.cameras.available=front,backyard
37 |
38 | ###################
39 | # Camera settings #
40 | ###################
41 | # This is the main configuration part. Each camera you want to use must be listed in sc.cameras.available
42 | # and configured here (camera id in brackets).
43 |
44 | ### Front door camera ###
45 | sc.camera[front].name=Front door
46 |
47 | # Camera host used for ping health check (see below). Only used when sc.healthcheck.enabled is set to true.
48 | sc.camera[front].host=192.168.1.30
49 |
50 | # Enable snapshot (live view) for camera. If enabled sc.camera[id].snapshot-url (see below) must be configured.
51 | sc.camera[front].snapshot-enabled=true
52 |
53 | # Enable live stream for camera. If enabled the snapshot url (see below) is used to generate a simple MJPEG stream
54 | # by requesting the JPG image periodically.
55 | sc.camera[front].stream-enabled=true
56 |
57 | # URL used to display snapshots (live view). This URL will not be exposed, all requests use the built-in proxy.
58 | sc.camera[front].snapshot-url=https://192.168.1.30/snapshot.cgi
59 |
60 | # Ftp username for incoming images. This is used for camera identification and must be unique!
61 | sc.camera[front].ftp.username=camera1
62 |
63 | # Ftp password for incoming images.
64 | sc.camera[front].ftp.password=password
65 |
66 | # Incoming ftp directory. This is the place where new surveillance images for this camera will be put for a short
67 | # period, until thumbnails are generated and they are moved to sc.image.storage-dir.
68 | sc.camera[front].ftp.incoming-dir=/home/surveillance/ftp/camera1/
69 |
70 | ###############
71 | # Data source #
72 | ###############
73 | # Datasource url. If you want stick to the hsqldb, make sure the file path matches to your environment.
74 | # If there is no existing database it will be created for you on application startup.
75 | spring.datasource.url=jdbc:hsqldb:file:/home/surveillance/db/surveillance.db
76 | ```
77 |
78 | After modifying the `application.properties` file don't forget to stop and
79 | restart the application to see your changes.
80 |
--------------------------------------------------------------------------------
/docs/developers.md:
--------------------------------------------------------------------------------
1 | ## Development environment
2 |
3 | If you want to develop new features or make bug fixes and need to compile the
4 | source code yourself, here are some notes on how to setup a local development
5 | environment.
6 |
7 | Make sure you have the [Java 8 Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
8 | installed on your system.
9 |
10 | To grab the source code of Surveillance Center, first clone the git repository:
11 |
12 | ```
13 | git clone https://github.com/1element/sc.git
14 | ```
15 |
16 | If you later want to contribute your code and make a pull request,
17 | you probably want to fork the repository first and clone your own fork.
18 | There are some additional notes for this in the [contributing document](https://github.com/1element/sc/blob/master/CONTRIBUTING.md).
19 |
20 | Checkout the `develop` branch as this is the branch where development happens.
21 |
22 | ```
23 | git checkout develop
24 | ```
25 |
26 |
27 | ### Server development
28 |
29 | The server (backend) part is written in Java using the Spring Boot framework.
30 |
31 | Gradle is used to build the project. For local development you can run
32 |
33 | ```
34 | ./gradlew bootRun
35 | ```
36 |
37 | in the root directory of the project. This will start the embedded Tomcat
38 | server on port 8080 and make your changes available. Make sure you have
39 | a proper configured `application.properties` file, either in
40 | `src/main/resources/application.properties` or somewhere else accessible.
41 |
42 |
43 | ### Client development
44 |
45 | The client (frontend) part is a Single Page Application (SPA) with
46 | PWA features (Progressive Web Application) written in Javascript (Vue.js).
47 |
48 | The sources are located in the `client` subdirectory.
49 |
50 | For development you need [Node.js](https://nodejs.org/) and npm
51 | (Node Package Manager) installed on your system. Npm ships with Node.js,
52 | so you don't have to install it separately.
53 |
54 | First of all run npm to install the dependencies:
55 |
56 | ```
57 | cd client
58 | npm install
59 | ```
60 |
61 | After this you can run for development:
62 |
63 | ```
64 | npm run dev
65 | ```
66 |
67 | This will start a local web server on port 8081 with hot-reload,
68 | Lint-on-save, etc.
69 |
70 | API requests are proxied to the Spring Boot tomcat server on port 8080, so make
71 | sure this is also running.
72 |
73 |
74 | ### Build
75 |
76 | The final build and executable jar file packaging is completely done using
77 | Gradle. You don't need to have Node.js and npm installed on your system. The
78 | gradle-node-plugin will take care of this.
79 |
80 | To build the project run
81 |
82 | ```
83 | ./gradlew build
84 | ```
85 |
86 | in the root directory.
87 |
--------------------------------------------------------------------------------
/docs/screenshots/screenshot1-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/docs/screenshots/screenshot1-thumbnail.png
--------------------------------------------------------------------------------
/docs/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/docs/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/docs/screenshots/screenshot2-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/docs/screenshots/screenshot2-thumbnail.png
--------------------------------------------------------------------------------
/docs/screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/docs/screenshots/screenshot2.png
--------------------------------------------------------------------------------
/docs/screenshots/screenshot3-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/docs/screenshots/screenshot3-thumbnail.png
--------------------------------------------------------------------------------
/docs/screenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/docs/screenshots/screenshot3.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1element/sc/99b1a78c9abe1c2cf0b315ac9af475997b2791fc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Nov 19 16:47:31 CET 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'client'
2 |
3 | rootProject.name = 'surveillancecenter'
4 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/SurveillanceCenterApplication.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc; //NOSONAR
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.autoconfigure.domain.EntityScan;
6 | import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
7 | import org.springframework.scheduling.annotation.EnableAsync;
8 | import org.springframework.scheduling.annotation.EnableScheduling;
9 |
10 | @EntityScan(basePackageClasses = {SurveillanceCenterApplication.class, Jsr310JpaConverters.class})
11 | @SpringBootApplication
12 | @EnableScheduling
13 | @EnableAsync
14 | public class SurveillanceCenterApplication {
15 |
16 | public static void main(String[] args) {
17 | SpringApplication.run(SurveillanceCenterApplication.class, args);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/FTPClientConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.apache.commons.net.ftp.FTPClient;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.annotation.Scope;
7 |
8 | /**
9 | * FTP client configuration bean.
10 | */
11 | @Configuration
12 | public class FTPClientConfiguration {
13 |
14 | @Bean
15 | @Scope("prototype")
16 | public FTPClient ftpClient() {
17 | return new FTPClient();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/Java8TimeDialectConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
6 |
7 | /**
8 | * Configuration class to support Java 8 time dialect.
9 | */
10 | @Configuration
11 | public class Java8TimeDialectConfiguration {
12 |
13 | @Bean
14 | public Java8TimeDialect java8TimeDialect() {
15 | return new Java8TimeDialect();
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/ModelMapperConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.modelmapper.ModelMapper;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | /**
8 | * ModelMapper configuration bean.
9 | * This allows mapping of entities/domain objects to DTOs (REST resources).
10 | */
11 | @Configuration
12 | public class ModelMapperConfiguration {
13 |
14 | @Bean
15 | public ModelMapper modelMapper() {
16 | return new ModelMapper();
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/PasswordEncoderConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
6 | import org.springframework.security.crypto.password.PasswordEncoder;
7 |
8 | /**
9 | * Password encoder configuration to use BCrypt.
10 | */
11 | @Configuration
12 | public class PasswordEncoderConfiguration {
13 |
14 | @Bean
15 | public PasswordEncoder passwordEncoder() {
16 | return new BCryptPasswordEncoder();
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/SFTPClientConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.context.annotation.Scope;
6 |
7 | import com.jcraft.jsch.JSch;
8 |
9 | /**
10 | * SFTP client configuration bean.
11 | */
12 | @Configuration
13 | public class SFTPClientConfiguration {
14 |
15 | @Bean
16 | @Scope("prototype")
17 | public JSch jsch() {
18 | return new JSch();
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/ServiceLocatorFactoryBeanConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.springframework.beans.factory.FactoryBean;
4 | import org.springframework.beans.factory.config.ServiceLocatorFactoryBean;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | import com.github._1element.sc.domain.pushnotification.PushNotificationClientFactory;
9 |
10 | /**
11 | * Service locator for factory beans.
12 | */
13 | @Configuration
14 | public class ServiceLocatorFactoryBeanConfiguration {
15 |
16 | /**
17 | * Service locator for factory beans.
18 | *
19 | * @return the factory bean
20 | */
21 | @Bean
22 | public FactoryBean serviceLocatorFactoryBean() {
23 | ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
24 | factoryBean.setServiceLocatorInterface(PushNotificationClientFactory.class);
25 |
26 | return factoryBean;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/configuration/StaticResourceConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.configuration; //NOSONAR
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.core.io.Resource;
6 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
8 |
9 | import com.github._1element.sc.properties.ImageProperties;
10 | import org.springframework.web.servlet.resource.PathResourceResolver;
11 |
12 | import java.io.IOException;
13 |
14 | /**
15 | * Configuration for static resources (images and client).
16 | */
17 | @Configuration
18 | public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
19 |
20 | public static final String IMAGES_PATH = "/images/";
21 |
22 | private ImageProperties imageProperties;
23 |
24 | @Autowired
25 | public StaticResourceConfiguration(ImageProperties imageProperties) {
26 | this.imageProperties = imageProperties;
27 | }
28 |
29 | @Override
30 | public void addResourceHandlers(ResourceHandlerRegistry registry) {
31 | // expose images directory as static resource
32 | registry.addResourceHandler(IMAGES_PATH + "**").addResourceLocations("file:" + imageProperties.getStorageDir());
33 |
34 | // map static path to single page app static assets
35 | registry.addResourceHandler("/static/**").addResourceLocations("classpath:/public/static/");
36 |
37 | // map all other paths to the single page app entry point (index.html)
38 | registry.addResourceHandler("/**").addResourceLocations("classpath:/public/index.html").resourceChain(true)
39 | .addResolver(new PathResourceResolver() {
40 | @Override
41 | protected Resource getResource(String resourcePath, Resource location) throws IOException {
42 | return location.exists() && location.isReadable() ? location : null;
43 | }
44 | });
45 |
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/controller/SurveillanceAuthenticationController.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.controller; //NOSONAR
2 |
3 | import com.github._1element.sc.security.JwtAuthenticationRequest;
4 | import com.github._1element.sc.service.JwtAuthenticationService;
5 | import com.github._1element.sc.utils.URIConstants;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.security.core.Authentication;
9 | import org.springframework.security.core.AuthenticationException;
10 | import org.springframework.security.core.context.SecurityContextHolder;
11 | import org.springframework.web.bind.annotation.PostMapping;
12 | import org.springframework.web.bind.annotation.RequestBody;
13 | import org.springframework.web.bind.annotation.RequestMapping;
14 | import org.springframework.web.bind.annotation.ResponseStatus;
15 | import org.springframework.web.bind.annotation.RestController;
16 |
17 | import javax.servlet.http.Cookie;
18 | import javax.servlet.http.HttpServletResponse;
19 |
20 | /**
21 | * REST controller to handle authentication login.
22 | */
23 | @RestController
24 | @RequestMapping(URIConstants.API_ROOT)
25 | public class SurveillanceAuthenticationController {
26 |
27 | private final JwtAuthenticationService jwtAuthenticationService;
28 |
29 | @Autowired
30 | public SurveillanceAuthenticationController(final JwtAuthenticationService jwtAuthenticationService) {
31 | this.jwtAuthenticationService = jwtAuthenticationService;
32 | }
33 |
34 | /**
35 | * Endpoint to create an authentication token, that will be returned as an http-only cookie.
36 | *
37 | * @param authenticationRequest the authentication request with username and password
38 | * @param response the http response
39 | *
40 | * @throws AuthenticationException if authentication failed
41 | */
42 | @PostMapping(URIConstants.API_AUTH)
43 | @ResponseStatus(HttpStatus.NO_CONTENT)
44 | public void createAuthenticationToken(@RequestBody final JwtAuthenticationRequest authenticationRequest,
45 | final HttpServletResponse response) throws AuthenticationException {
46 |
47 | final Authentication authentication = jwtAuthenticationService.attemptAuthentication(
48 | authenticationRequest.getUsername(), authenticationRequest.getPassword());
49 |
50 | SecurityContextHolder.getContext().setAuthentication(authentication);
51 |
52 | final Cookie cookie = jwtAuthenticationService.generateTokenCookie(authenticationRequest.getUsername());
53 | response.addCookie(cookie);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/controller/SurveillanceFeedController.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.controller; //NOSONAR
2 |
3 | import com.github._1element.sc.dto.ImagesCameraSummaryResult;
4 | import com.github._1element.sc.service.SurveillanceService;
5 | import com.github._1element.sc.utils.URIConstants;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Controller;
8 | import org.springframework.ui.Model;
9 | import org.springframework.web.bind.annotation.GetMapping;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 |
12 | import java.util.List;
13 |
14 | @Controller
15 | @RequestMapping(URIConstants.FEED_ROOT)
16 | public class SurveillanceFeedController {
17 |
18 | private final SurveillanceService surveillanceService;
19 |
20 | @Autowired
21 | public SurveillanceFeedController(final SurveillanceService surveillanceService) {
22 | this.surveillanceService = surveillanceService;
23 | }
24 |
25 | /**
26 | * Renders RSS status feed displaying a summary for each camera.
27 | *
28 | * @param model the spring model
29 | * @return rendered RSS feed
30 | */
31 | @GetMapping(URIConstants.FEED_CAMERAS)
32 | public String camerasfeed(final Model model) {
33 | final List imagesCameraSummaryResult = surveillanceService.getImagesCameraSummary();
34 |
35 | model.addAttribute("imagesCameraSummaryResult", imagesCameraSummaryResult);
36 |
37 | return "feed-cameras";
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/controller/SurveillanceProxyController.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.controller; //NOSONAR
2 |
3 | import com.github._1element.sc.domain.Camera;
4 | import com.github._1element.sc.exception.CameraNotFoundException;
5 | import com.github._1element.sc.exception.ProxyException;
6 | import com.github._1element.sc.repository.CameraRepository;
7 | import com.github._1element.sc.service.SurveillanceProxyService;
8 | import com.github._1element.sc.utils.URIConstants;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.stereotype.Controller;
12 | import org.springframework.web.bind.annotation.GetMapping;
13 | import org.springframework.web.bind.annotation.PathVariable;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 |
16 | @Controller
17 | @RequestMapping(URIConstants.PROXY_ROOT)
18 | public class SurveillanceProxyController {
19 |
20 | private final SurveillanceProxyService proxyService;
21 |
22 | private final CameraRepository cameraRepository;
23 |
24 | @Autowired
25 | public SurveillanceProxyController(final SurveillanceProxyService proxyService,
26 | final CameraRepository cameraRepository) {
27 | this.proxyService = proxyService;
28 | this.cameraRepository = cameraRepository;
29 | }
30 |
31 | /**
32 | * Retrieves a new snapshot for the provided camera (proxy).
33 | *
34 | * @param id the camera id to retrieve snapshot for
35 | * @return retrieved snapshot image
36 | * @throws CameraNotFoundException if camera id was not found
37 | */
38 | @GetMapping(URIConstants.PROXY_SNAPSHOT)
39 | public ResponseEntity retrieveSnapshot(@PathVariable final String id)
40 | throws CameraNotFoundException, ProxyException {
41 |
42 | final Camera camera = cameraRepository.findById(id);
43 |
44 | if (camera == null) {
45 | throw new CameraNotFoundException();
46 | }
47 |
48 | return proxyService.retrieveImage(camera.getPicture().getSnapshotUrl());
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/controller/SurveillanceStreamGenerationController.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.controller; //NOSONAR
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import com.github._1element.sc.domain.Camera;
8 | import com.github._1element.sc.exception.CameraNotFoundException;
9 | import com.github._1element.sc.repository.CameraRepository;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.stereotype.Controller;
14 | import org.springframework.web.bind.annotation.GetMapping;
15 | import org.springframework.web.bind.annotation.PathVariable;
16 | import org.springframework.web.bind.annotation.RequestMapping;
17 | import org.springframework.web.client.RestClientException;
18 |
19 | import com.github._1element.sc.exception.ForbiddenException;
20 | import com.github._1element.sc.service.MjpegGenerationService;
21 | import com.github._1element.sc.utils.URIConstants;
22 |
23 | @Controller
24 | @RequestMapping(URIConstants.GENERATE_ROOT)
25 | public class SurveillanceStreamGenerationController {
26 |
27 | private final CameraRepository cameraRepository;
28 |
29 | private final MjpegGenerationService mjpegGenerationService;
30 |
31 | private static final Logger LOG = LoggerFactory.getLogger(SurveillanceStreamGenerationController.class);
32 |
33 | /**
34 | * Constructs a SurveillanceStreamGenerationController.
35 | *
36 | * @param cameraRepository the camera repository
37 | * @param mjpegGenerationService the MJPEG generation service
38 | */
39 | @Autowired
40 | public SurveillanceStreamGenerationController(final CameraRepository cameraRepository,
41 | final MjpegGenerationService mjpegGenerationService) {
42 | this.cameraRepository = cameraRepository;
43 | this.mjpegGenerationService = mjpegGenerationService;
44 | }
45 |
46 | /**
47 | * Creates a simple MJPEG stream by requesting a camera snapshot JPG URL periodically.
48 | *
49 | * @param id the camera id to create stream for
50 | * @param response the streaming HTTP response
51 | * @throws ForbiddenException exception if MJPEG stream is disabled by configuration
52 | * @throws CameraNotFoundException exception if provided camera id could not be found
53 | */
54 | @GetMapping(URIConstants.GENERATE_MJPEG)
55 | public void generateMJPEG(@PathVariable final String id, final HttpServletResponse response)
56 | throws ForbiddenException, CameraNotFoundException {
57 |
58 | final Camera camera = cameraRepository.findById(id);
59 |
60 | if (camera == null) {
61 | throw new CameraNotFoundException();
62 | }
63 |
64 | if (!camera.getPicture().isStreamEnabled()) {
65 | throw new ForbiddenException("MJPEG stream generation is disabled.");
66 | }
67 |
68 | mjpegGenerationService.setContentType(response);
69 | mjpegGenerationService.setCacheControlHeader(response);
70 |
71 | try {
72 | mjpegGenerationService.writeSnapshotToOutputStream(camera.getPicture().getSnapshotUrl(), response);
73 | } catch (IOException | RestClientException exception) {
74 | LOG.debug("MJPEG streaming terminated: {}", exception.getMessage());
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/Camera.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain; //NOSONAR
2 |
3 | /**
4 | * Surveillance camera POJO.
5 | */
6 | public class Camera {
7 |
8 | private final String id;
9 |
10 | private final String name;
11 |
12 | private final String host;
13 |
14 | private final String mqttTopic;
15 |
16 | private final CameraFtp ftp;
17 |
18 | private final CameraPicture picture;
19 |
20 | /**
21 | * Constructs a new camera.
22 | *
23 | * @param id the unique id of the camera
24 | * @param name the camera name
25 | * @param host the (internal) host the camera is running on
26 | * @param mqttTopic the mqtt topic
27 | * @param ftp the ftp settings
28 | * @param picture the picture settings
29 | */
30 | public Camera(final String id, final String name, final String host,
31 | final String mqttTopic, final CameraFtp ftp, final CameraPicture picture) {
32 | this.id = id;
33 | this.name = name;
34 | this.host = host;
35 | this.ftp = ftp;
36 | this.picture = picture;
37 | this.mqttTopic = mqttTopic;
38 | }
39 |
40 | public String getId() {
41 | return id;
42 | }
43 |
44 | public String getName() {
45 | return name;
46 | }
47 |
48 | public String getHost() {
49 | return host;
50 | }
51 |
52 | public CameraFtp getFtp() {
53 | return ftp;
54 | }
55 |
56 | public CameraPicture getPicture() {
57 | return picture;
58 | }
59 |
60 | public String getMqttTopic() {
61 | return mqttTopic;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/CameraFtp.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain; //NOSONAR
2 |
3 | /**
4 | * Camera ftp value object.
5 | */
6 | public class CameraFtp {
7 |
8 | private final String username;
9 | private final String password;
10 | private final String incomingDirectory;
11 |
12 | /**
13 | * Constructs a new camera ftp value object.
14 | *
15 | * @param username the ftp username for incoming files
16 | * @param password the ftp password for incoming files
17 | * @param incomingDirectory the ftp incoming directory
18 | */
19 | public CameraFtp(final String username, final String password, final String incomingDirectory) {
20 | this.username = username;
21 | this.password = password;
22 | this.incomingDirectory = incomingDirectory;
23 | }
24 |
25 | public String getUsername() {
26 | return username;
27 | }
28 |
29 | public String getPassword() {
30 | return password;
31 | }
32 |
33 | public String getIncomingDirectory() {
34 | return incomingDirectory;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/CameraPicture.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain; //NOSONAR
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 |
5 | /**
6 | * Camera picture value object.
7 | */
8 | public class CameraPicture {
9 |
10 | private final String snapshotUrl;
11 | private final boolean snapshotEnabled;
12 | private final boolean streamEnabled;
13 |
14 | /**
15 | * Constructs a new camera picture value object.
16 | *
17 | * @param snapshotUrl optional url to retrieve snapshots
18 | * @param snapshotEnabled true if snapshots are enabled
19 | * @param streamEnabled true if streaming is enabled
20 | */
21 | public CameraPicture(final String snapshotUrl, final boolean snapshotEnabled, final boolean streamEnabled) {
22 | if ((snapshotEnabled || streamEnabled) && StringUtils.isBlank(snapshotUrl)) {
23 | throw new IllegalArgumentException("Snapshot-url must be provided if snapshot-enabled or stream-enabled.");
24 | }
25 |
26 | this.snapshotUrl = snapshotUrl;
27 | this.snapshotEnabled = snapshotEnabled;
28 | this.streamEnabled = streamEnabled;
29 | }
30 |
31 | public String getSnapshotUrl() {
32 | return snapshotUrl;
33 | }
34 |
35 | public boolean isSnapshotEnabled() {
36 | return snapshotEnabled;
37 | }
38 |
39 | public boolean isStreamEnabled() {
40 | return streamEnabled;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/PushNotificationSetting.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain; //NOSONAR
2 |
3 | import javax.persistence.Entity;
4 | import javax.persistence.GeneratedValue;
5 | import javax.persistence.GenerationType;
6 | import javax.persistence.Id;
7 |
8 | /**
9 | * Entity for push notification settings.
10 | */
11 | @Entity
12 | public class PushNotificationSetting {
13 |
14 | @Id
15 | @GeneratedValue(strategy = GenerationType.AUTO)
16 | private long id;
17 |
18 | private String cameraId;
19 |
20 | private boolean enabled = false;
21 |
22 | protected PushNotificationSetting() {
23 | }
24 |
25 | public PushNotificationSetting(final String cameraId, final boolean enabled) {
26 | this.cameraId = cameraId;
27 | this.enabled = enabled;
28 | }
29 |
30 | public String getCameraId() {
31 | return cameraId;
32 | }
33 |
34 | public boolean isEnabled() {
35 | return enabled;
36 | }
37 |
38 | public void setEnabled(final boolean enabled) {
39 | this.enabled = enabled;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/SurveillanceImage.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain; //NOSONAR
2 |
3 | import javax.persistence.Entity;
4 | import javax.persistence.GeneratedValue;
5 | import javax.persistence.GenerationType;
6 | import javax.persistence.Id;
7 | import java.time.LocalDateTime;
8 | import java.util.Objects;
9 |
10 | /**
11 | * Surveillance image entity.
12 | */
13 | @Entity
14 | public class SurveillanceImage {
15 |
16 | @Id
17 | @GeneratedValue(strategy = GenerationType.AUTO)
18 | private long id;
19 |
20 | private String fileName;
21 |
22 | private String cameraId;
23 |
24 | private LocalDateTime receivedAt;
25 |
26 | private final boolean archived = false;
27 |
28 | protected SurveillanceImage() {
29 | }
30 |
31 | /**
32 | * Constructs a new surveillance image.
33 | *
34 | * @param fileName the image file name
35 | * @param cameraId the camera identifier
36 | * @param receivedAt the received at time
37 | */
38 | public SurveillanceImage(final String fileName, final String cameraId, final LocalDateTime receivedAt) {
39 | this.fileName = fileName;
40 | this.cameraId = cameraId;
41 | this.receivedAt = receivedAt;
42 | }
43 |
44 | public long getId() {
45 | return id;
46 | }
47 |
48 | public String getFileName() {
49 | return fileName;
50 | }
51 |
52 | public LocalDateTime getReceivedAt() {
53 | return receivedAt;
54 | }
55 |
56 | public boolean isArchived() {
57 | return archived;
58 | }
59 |
60 | public String getCameraId() {
61 | return cameraId;
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return String.valueOf(id);
67 | }
68 |
69 | @Override
70 | public boolean equals(final Object other) {
71 | if (!(other instanceof SurveillanceImage)) {
72 | return false;
73 | }
74 | final SurveillanceImage castOther = (SurveillanceImage) other;
75 | return Objects.equals(id, castOther.id) && Objects.equals(fileName, castOther.fileName)
76 | && Objects.equals(cameraId, castOther.cameraId) && Objects.equals(receivedAt, castOther.receivedAt)
77 | && Objects.equals(archived, castOther.archived);
78 | }
79 |
80 | @Override
81 | public int hashCode() {
82 | return Objects.hash(id, fileName, cameraId, receivedAt, archived);
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/SurveillanceProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain; //NOSONAR
2 |
3 | import com.github._1element.sc.configuration.StaticResourceConfiguration;
4 | import org.springframework.stereotype.Component;
5 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
6 |
7 | /**
8 | * Surveillance properties component class.
9 | * This will expose some programmatic properties.
10 | */
11 | @Component
12 | public class SurveillanceProperties {
13 |
14 | private static final String IMAGE_THUMBNAIL_PREFIX = "thumbnail.";
15 |
16 | public String getImageThumbnailPrefix() {
17 | return IMAGE_THUMBNAIL_PREFIX;
18 | }
19 |
20 | public String getImageBaseUrl() {
21 | return ServletUriComponentsBuilder.fromCurrentContextPath().path(StaticResourceConfiguration.IMAGES_PATH)
22 | .build().toString();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/pushnotification/PushNotificationClient.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.pushnotification; //NOSONAR
2 |
3 | import com.github._1element.sc.exception.PushNotificationClientException;
4 |
5 | /**
6 | * Interface for all push notification clients.
7 | */
8 | public interface PushNotificationClient {
9 |
10 | void sendMessage(String title, String text) throws PushNotificationClientException;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/pushnotification/PushNotificationClientFactory.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.pushnotification; //NOSONAR
2 |
3 | /**
4 | * Factory to create a push notification client adapter.
5 | */
6 | public interface PushNotificationClientFactory {
7 |
8 | /**
9 | * Returns the push notification client.
10 | *
11 | * @param name the client adapter name
12 | * @return the push notification client
13 | */
14 | public PushNotificationClient getClient(String name);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/pushnotification/PushoverClient.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.pushnotification; //NOSONAR
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.web.client.RestTemplateBuilder;
7 | import org.springframework.stereotype.Component;
8 | import org.springframework.util.LinkedMultiValueMap;
9 | import org.springframework.util.MultiValueMap;
10 | import org.springframework.web.client.RestClientException;
11 | import org.springframework.web.client.RestTemplate;
12 |
13 | import com.github._1element.sc.exception.PushNotificationClientException;
14 | import com.github._1element.sc.properties.PushNotificationProperties;
15 |
16 | /**
17 | * Pushover client adapter.
18 | * This will send push notifications using the pushover.net API.
19 | */
20 | @Component("pushover")
21 | public class PushoverClient implements PushNotificationClient {
22 |
23 | private final RestTemplate restTemplate;
24 |
25 | private PushNotificationProperties properties;
26 |
27 | private static final String ENDPOINT = "https://api.pushover.net/1/messages.json";
28 |
29 | private static final String PARAM_API_TOKEN = "token";
30 |
31 | private static final String PARAM_USER_TOKEN = "user";
32 |
33 | private static final String PARAM_TITLE = "title";
34 |
35 | private static final String PARAM_MESSAGE = "message";
36 |
37 | private static final Logger LOG = LoggerFactory.getLogger(PushoverClient.class);
38 |
39 | @Autowired
40 | public PushoverClient(RestTemplateBuilder restTemplateBuilder, PushNotificationProperties properties) {
41 | this.restTemplate = restTemplateBuilder.build();
42 | this.properties = properties;
43 | }
44 |
45 | @Override
46 | public void sendMessage(String title, String text) throws PushNotificationClientException {
47 | // pushover does not support receiving json
48 | // so use a MultiValueMap that will be converted to application/x-www-form-urlencoded
49 | MultiValueMap requestParams = new LinkedMultiValueMap<>();
50 | requestParams.add(PARAM_API_TOKEN, properties.getApiToken());
51 | requestParams.add(PARAM_USER_TOKEN, properties.getUserToken());
52 | requestParams.add(PARAM_TITLE, title);
53 | requestParams.add(PARAM_MESSAGE, text);
54 |
55 | try {
56 | restTemplate.postForObject(ENDPOINT, requestParams, Void.class);
57 | } catch (RestClientException exception) {
58 | LOG.error("Error while sending push notification: {}", exception.getMessage());
59 | throw new PushNotificationClientException(exception);
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/remotecopy/AbstractFTPRemoteCopy.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.remotecopy; //NOSONAR
2 |
3 | import java.io.IOException;
4 |
5 | import org.apache.commons.net.ftp.FTP;
6 | import org.apache.commons.net.ftp.FTPClient;
7 | import org.apache.commons.net.ftp.FTPReply;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 |
10 | import com.github._1element.sc.exception.FTPRemoteCopyException;
11 | import com.github._1element.sc.properties.FTPRemoteCopyProperties;
12 | import com.github._1element.sc.service.FileService;
13 |
14 | /**
15 | * Abstract FTP remote copy class.
16 | */
17 | public abstract class AbstractFTPRemoteCopy {
18 |
19 | protected FTPRemoteCopyProperties ftpRemoteCopyProperties;
20 |
21 | protected FTPClient ftp;
22 |
23 | protected FileService fileService;
24 |
25 | /**
26 | * Constructor.
27 | *
28 | * @param ftpRemoteCopyProperties the properties to use for remote copying
29 | * @param ftp the ftp client dependency
30 | * @param fileService the file service dependency
31 | */
32 | @Autowired
33 | public AbstractFTPRemoteCopy(FTPRemoteCopyProperties ftpRemoteCopyProperties, FTPClient ftp,
34 | FileService fileService) {
35 | this.ftpRemoteCopyProperties = ftpRemoteCopyProperties;
36 | this.ftp = ftp;
37 | this.fileService = fileService;
38 | }
39 |
40 | /**
41 | * Connect to FTP server.
42 | *
43 | * @throws FTPRemoteCopyException exception if connection or login to remote was not successful
44 | * @throws IOException exception if IO error occurred during connection
45 | */
46 | protected void connect() throws FTPRemoteCopyException, IOException {
47 | ftp.connect(ftpRemoteCopyProperties.getHost());
48 |
49 | if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
50 | throw new FTPRemoteCopyException(String.format("Could not connect to remote ftp server '%s'. Response was: %s",
51 | ftpRemoteCopyProperties.getHost(), ftp.getReplyString()));
52 | }
53 |
54 | if (!ftp.login(ftpRemoteCopyProperties.getUsername(), ftpRemoteCopyProperties.getPassword())) {
55 | throw new FTPRemoteCopyException("Could not login to remote ftp server. Invalid username or password.");
56 | }
57 |
58 | ftp.setFileType(FTP.BINARY_FILE_TYPE);
59 | ftp.enterLocalPassiveMode();
60 | }
61 |
62 | /**
63 | * Disconnect from FTP server.
64 | */
65 | protected void disconnect() {
66 | if (ftp != null && ftp.isConnected()) {
67 | try {
68 | ftp.logout();
69 | ftp.disconnect();
70 | } catch (IOException exception) {
71 | // silently ignore disconnect exceptions
72 | }
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/remotecopy/AbstractSFTPRemoteCopy.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.remotecopy; //NOSONAR
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 |
5 | import com.github._1element.sc.exception.SFTPRemoteCopyException;
6 | import com.github._1element.sc.properties.SFTPRemoteCopyProperties;
7 | import com.github._1element.sc.service.FileService;
8 | import com.jcraft.jsch.Channel;
9 | import com.jcraft.jsch.ChannelSftp;
10 | import com.jcraft.jsch.JSch;
11 | import com.jcraft.jsch.JSchException;
12 | import com.jcraft.jsch.Session;
13 |
14 | /**
15 | * Abstract SFTP remote copy class.
16 | */
17 | public class AbstractSFTPRemoteCopy {
18 |
19 | protected SFTPRemoteCopyProperties sftpRemoteCopyProperties;
20 |
21 | protected FileService fileService;
22 |
23 | private JSch jsch;
24 |
25 | private Session session;
26 |
27 | private static final String SFTP_CHANNEL_NAME = "sftp";
28 |
29 | private static final String CONFIG_STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
30 |
31 | private static final String CONFIG_DISABLED = "no";
32 |
33 | /**
34 | * Constructor.
35 | *
36 | * @param sftpRemoteCopyProperties the properties to use for remote copying
37 | * @param jsch the Jsch dependency used for SSH connections
38 | * @param fileService the file service dependency
39 | */
40 | @Autowired
41 | public AbstractSFTPRemoteCopy(SFTPRemoteCopyProperties sftpRemoteCopyProperties, JSch jsch, FileService fileService) {
42 | this.sftpRemoteCopyProperties = sftpRemoteCopyProperties;
43 | this.jsch = jsch;
44 | this.fileService = fileService;
45 | }
46 |
47 | /**
48 | * Creates a SFTP channel.
49 | *
50 | * @return SFTP channel
51 | * @throws SFTPRemoteCopyException exception in case of an error
52 | */
53 | protected ChannelSftp createSFTPChannel() throws SFTPRemoteCopyException {
54 | Channel channel;
55 | try {
56 | session = jsch.getSession(sftpRemoteCopyProperties.getUsername(), sftpRemoteCopyProperties.getHost());
57 | session.setConfig(CONFIG_STRICT_HOST_KEY_CHECKING, CONFIG_DISABLED);
58 | session.setPassword(sftpRemoteCopyProperties.getPassword());
59 | session.connect();
60 | channel = session.openChannel(SFTP_CHANNEL_NAME);
61 | } catch (JSchException exception) {
62 | session.disconnect();
63 | throw new SFTPRemoteCopyException(String.format("Could not establish SSH connection: %s",
64 | exception.getMessage()), exception);
65 | }
66 |
67 | if (channel == null) {
68 | session.disconnect();
69 | throw new SFTPRemoteCopyException("No channel was found.");
70 | }
71 |
72 | try {
73 | channel.connect();
74 | } catch (JSchException exception) {
75 | session.disconnect();
76 | throw new SFTPRemoteCopyException(String.format("Could not establish SFTP channel: %s",
77 | exception.getMessage()), exception);
78 | }
79 |
80 | if (!(channel instanceof ChannelSftp)) {
81 | channel.disconnect();
82 | session.disconnect();
83 | throw new SFTPRemoteCopyException("No SFTP channel was found.");
84 | }
85 |
86 | return (ChannelSftp) channel;
87 | }
88 |
89 | /**
90 | * Disconnect SSH session if existing.
91 | */
92 | protected void disconnectSession() {
93 | if (session != null) {
94 | session.disconnect();
95 | }
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/remotecopy/FTPRemoteCopy.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.remotecopy; //NOSONAR
2 |
3 | import com.github._1element.sc.events.RemoteCopyEvent;
4 | import com.github._1element.sc.exception.FTPRemoteCopyException;
5 | import com.github._1element.sc.properties.FTPRemoteCopyProperties;
6 | import com.github._1element.sc.service.FileService;
7 |
8 | import org.apache.commons.net.ftp.FTPClient;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
13 | import org.springframework.context.annotation.Scope;
14 | import org.springframework.stereotype.Component;
15 |
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.nio.file.Path;
19 |
20 | /**
21 | * Copy surveillance image to FTP remote server (backup).
22 | */
23 | @ConditionalOnProperty(name = "sc.remotecopy.ftp.enabled", havingValue = "true")
24 | @Component
25 | @Scope("prototype")
26 | public class FTPRemoteCopy extends AbstractFTPRemoteCopy implements RemoteCopy {
27 |
28 | private static final Logger LOG = LoggerFactory.getLogger(FTPRemoteCopy.class);
29 |
30 | @Autowired
31 | public FTPRemoteCopy(FTPRemoteCopyProperties ftpRemoteCopyProperties, FTPClient ftp, FileService fileService) {
32 | super(ftpRemoteCopyProperties, ftp, fileService);
33 | }
34 |
35 | @Override
36 | public void handle(RemoteCopyEvent remoteCopyEvent) {
37 | LOG.debug("FTP remote copy handler for '{}' invoked.", remoteCopyEvent.getFileName());
38 |
39 | try {
40 | connect();
41 | transferFile(remoteCopyEvent.getFileName());
42 | } catch (Exception exception) {
43 | LOG.warn("Error during remote FTP copy: {}", exception.getMessage());
44 | } finally {
45 | disconnect();
46 | }
47 | }
48 |
49 | /**
50 | * Transfer file to FTP server.
51 | *
52 | * @param completeLocalFilePath complete path to the local file
53 | * @throws FTPRemoteCopyException exception if file could not be uploaded
54 | * @throws IOException exception in case of an IO error
55 | */
56 | private void transferFile(String completeLocalFilePath) throws FTPRemoteCopyException, IOException {
57 | Path path = fileService.getPath(completeLocalFilePath);
58 |
59 | try (InputStream inputStream = fileService.createInputStream(path)) {
60 | if (!ftp.storeFile(ftpRemoteCopyProperties.getDir() + path.getFileName().toString(), inputStream)) {
61 | throw new FTPRemoteCopyException(String.format("Could not upload file to remote FTP server. Response was: %s",
62 | ftp.getReplyString()));
63 | }
64 | }
65 |
66 | LOG.info("File '{}' was successfully uploaded to remote FTP server.", path.getFileName().toString());
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/remotecopy/RemoteCopy.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.remotecopy; //NOSONAR
2 |
3 | import com.github._1element.sc.events.RemoteCopyEvent;
4 | import org.springframework.context.event.EventListener;
5 |
6 | /**
7 | * Remote copy interface.
8 | */
9 | public interface RemoteCopy {
10 |
11 | /**
12 | * Listen to remote copy events and handle copy action.
13 | *
14 | * @param remoteCopyEvent remote copy event
15 | */
16 | @EventListener
17 | void handle(RemoteCopyEvent remoteCopyEvent);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/remotecopy/RemoteCopyCleanup.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.remotecopy; //NOSONAR
2 |
3 | /**
4 | * Interface for remote copy cleanup components.
5 | */
6 | public interface RemoteCopyCleanup {
7 |
8 | /**
9 | * Remove old files. Should be scheduled.
10 | */
11 | void cleanup();
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/domain/remotecopy/SFTPRemoteCopy.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.domain.remotecopy; //NOSONAR
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.nio.file.Path;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11 | import org.springframework.context.annotation.Scope;
12 | import org.springframework.stereotype.Component;
13 |
14 | import com.github._1element.sc.events.RemoteCopyEvent;
15 | import com.github._1element.sc.exception.SFTPRemoteCopyException;
16 | import com.github._1element.sc.properties.SFTPRemoteCopyProperties;
17 | import com.github._1element.sc.service.FileService;
18 | import com.jcraft.jsch.ChannelSftp;
19 | import com.jcraft.jsch.JSch;
20 | import com.jcraft.jsch.SftpException;
21 |
22 | /**
23 | * Copy surveillance image to SFTP remote server (backup).
24 | */
25 | @ConditionalOnProperty(name = "sc.remotecopy.sftp.enabled", havingValue = "true")
26 | @Component
27 | @Scope("prototype")
28 | public class SFTPRemoteCopy extends AbstractSFTPRemoteCopy implements RemoteCopy {
29 |
30 | private static final Logger LOG = LoggerFactory.getLogger(SFTPRemoteCopy.class);
31 |
32 | @Autowired
33 | public SFTPRemoteCopy(SFTPRemoteCopyProperties sftpRemoteCopyProperties, JSch jsch, FileService fileService) {
34 | super(sftpRemoteCopyProperties, jsch, fileService);
35 | }
36 |
37 | @Override
38 | public void handle(RemoteCopyEvent remoteCopyEvent) {
39 | LOG.debug("SFTP remote copy handler for '{}' invoked.", remoteCopyEvent.getFileName());
40 |
41 | ChannelSftp sftpChannel = null;
42 | try {
43 | sftpChannel = createSFTPChannel();
44 | transferFile(remoteCopyEvent.getFileName(), sftpChannel);
45 | } catch (SFTPRemoteCopyException exception) {
46 | LOG.warn("Error during remote SFTP copy: '{}'", exception.getMessage());
47 | } finally {
48 | if (sftpChannel != null) {
49 | sftpChannel.disconnect();
50 | }
51 | disconnectSession();
52 | }
53 | }
54 |
55 | /**
56 | * Uploads a file using the given SFTP channel.
57 | *
58 | * @param completeLocalFilePath the full path to the local file to upload
59 | * @param sftpChannel the SFTP channel that will be used for the transfer
60 | * @throws SFTPRemoteCopyException exception in case of an error
61 | */
62 | private void transferFile(String completeLocalFilePath, ChannelSftp sftpChannel) throws SFTPRemoteCopyException {
63 | Path path = fileService.getPath(completeLocalFilePath);
64 |
65 | try (InputStream inputStream = fileService.createInputStream(path)) {
66 | sftpChannel.cd(sftpRemoteCopyProperties.getDir());
67 | sftpChannel.put(inputStream, path.getFileName().toString());
68 | } catch (SftpException | IOException exception) {
69 | throw new SFTPRemoteCopyException("Could not upload file to remote SFTP server: " + exception.getMessage(),
70 | exception);
71 | }
72 |
73 | LOG.info("File '{}' was successfully uploaded to remote SFTP server.", path.getFileName().toString());
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/CameraResource.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | /**
4 | * Camera REST resource.
5 | * Similar to {@link com.github._1element.sc.domain.Camera} but with additional/limited attributes.
6 | * We do not want to expose our internal entity so this DTO is used.
7 | */
8 | public class CameraResource {
9 |
10 | private String id;
11 |
12 | private String name;
13 |
14 | private String snapshotProxyUrl;
15 |
16 | private String streamGeneratorUrl;
17 |
18 | public String getId() {
19 | return id;
20 | }
21 |
22 | public void setId(final String id) {
23 | this.id = id;
24 | }
25 |
26 | public String getName() {
27 | return name;
28 | }
29 |
30 | public void setName(final String name) {
31 | this.name = name;
32 | }
33 |
34 | public String getSnapshotProxyUrl() {
35 | return snapshotProxyUrl;
36 | }
37 |
38 | public void setSnapshotProxyUrl(final String snapshotProxyUrl) {
39 | this.snapshotProxyUrl = snapshotProxyUrl;
40 | }
41 |
42 | public String getStreamGeneratorUrl() {
43 | return streamGeneratorUrl;
44 | }
45 |
46 | public void setStreamGeneratorUrl(final String streamGeneratorUrl) {
47 | this.streamGeneratorUrl = streamGeneratorUrl;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/ImagesCameraSummaryResult.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | import java.time.LocalDateTime;
4 |
5 | import com.github._1element.sc.domain.Camera;
6 | import java.util.Objects;
7 |
8 | /**
9 | * Summary of surveillance images for each camera (count and most recent image date).
10 | */
11 | public class ImagesCameraSummaryResult {
12 |
13 | private final Camera camera;
14 |
15 | private final Long count;
16 |
17 | private final LocalDateTime mostRecentDate;
18 |
19 | /**
20 | * Constructs a new summary of surveillance images for each camera.
21 | *
22 | * @param camera the camera
23 | * @param count the count of images
24 | * @param mostRecentDate the most recent image date
25 | */
26 | public ImagesCameraSummaryResult(final Camera camera, final Long count, final LocalDateTime mostRecentDate) {
27 | this.camera = camera;
28 | this.count = count;
29 | this.mostRecentDate = mostRecentDate;
30 | }
31 |
32 | public Camera getCamera() {
33 | return camera;
34 | }
35 |
36 | public Long getCount() {
37 | return count;
38 | }
39 |
40 | public LocalDateTime getMostRecentDate() {
41 | return mostRecentDate;
42 | }
43 |
44 | @Override
45 | public boolean equals(final Object other) {
46 | if (!(other instanceof ImagesCameraSummaryResult)) {
47 | return false;
48 | }
49 | final ImagesCameraSummaryResult castOther = (ImagesCameraSummaryResult) other;
50 | return Objects.equals(camera, castOther.camera) && Objects.equals(count, castOther.count)
51 | && Objects.equals(mostRecentDate, castOther.mostRecentDate);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return Objects.hash(camera, count, mostRecentDate);
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/PushNotificationSettingResource.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | /**
4 | * Push notification setting resource.
5 | * REST projection for the internal {@link com.github._1element.sc.domain.PushNotificationSetting} entity.
6 | */
7 | public class PushNotificationSettingResource {
8 |
9 | private final String cameraId;
10 |
11 | private final String cameraName;
12 |
13 | private final boolean enabled;
14 |
15 | /**
16 | * Constructor.
17 | *
18 | * @param cameraId the camera identifier
19 | * @param cameraName the camera name
20 | * @param enabled the push notification status (enabled/disabled)
21 | */
22 | public PushNotificationSettingResource(final String cameraId, final String cameraName, final boolean enabled) {
23 | this.cameraId = cameraId;
24 | this.cameraName = cameraName;
25 | this.enabled = enabled;
26 | }
27 |
28 | public String getCameraId() {
29 | return cameraId;
30 | }
31 |
32 | public String getCameraName() {
33 | return cameraName;
34 | }
35 |
36 | public boolean isEnabled() {
37 | return enabled;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/PushNotificationSettingUpdateResource.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | /**
4 | * DTO used to update {@link com.github._1element.sc.domain.PushNotificationSetting}.
5 | */
6 | public class PushNotificationSettingUpdateResource {
7 |
8 | private String cameraId;
9 |
10 | private boolean enabled;
11 |
12 | public String getCameraId() {
13 | return cameraId;
14 | }
15 |
16 | public void setCameraId(final String cameraId) {
17 | this.cameraId = cameraId;
18 | }
19 |
20 | public boolean isEnabled() {
21 | return enabled;
22 | }
23 |
24 | public void setEnabled(final boolean enabled) {
25 | this.enabled = enabled;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/SurveillanceImageBulkUpdateResource.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | import java.time.LocalDateTime;
4 |
5 | /**
6 | * DTO used to bulk update all {@link com.github._1element.sc.domain.SurveillanceImage} before a provided timestamp.
7 | */
8 | public class SurveillanceImageBulkUpdateResource {
9 |
10 | private LocalDateTime dateBefore;
11 |
12 | private boolean archived;
13 |
14 | public LocalDateTime getDateBefore() {
15 | return dateBefore;
16 | }
17 |
18 | public void setDateBefore(final LocalDateTime dateBefore) {
19 | this.dateBefore = dateBefore;
20 | }
21 |
22 | public boolean isArchived() {
23 | return archived;
24 | }
25 |
26 | public void setArchived(final boolean archived) {
27 | this.archived = archived;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/SurveillanceImageResource.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | import java.time.LocalDateTime;
4 |
5 | /**
6 | * REST projection for the internal {@link com.github._1element.sc.domain.SurveillanceImage} entity.
7 | */
8 | public class SurveillanceImageResource {
9 |
10 | private long id;
11 |
12 | private String fileName;
13 |
14 | private String cameraId;
15 |
16 | private String cameraName;
17 |
18 | private LocalDateTime receivedAt;
19 |
20 | private boolean archived;
21 |
22 | public long getId() {
23 | return id;
24 | }
25 |
26 | public void setId(final long id) {
27 | this.id = id;
28 | }
29 |
30 | public String getFileName() {
31 | return fileName;
32 | }
33 |
34 | public void setFileName(final String fileName) {
35 | this.fileName = fileName;
36 | }
37 |
38 | public String getCameraId() {
39 | return cameraId;
40 | }
41 |
42 | public void setCameraId(final String cameraId) {
43 | this.cameraId = cameraId;
44 | }
45 |
46 | public String getCameraName() {
47 | return cameraName;
48 | }
49 |
50 | public void setCameraName(final String cameraName) {
51 | this.cameraName = cameraName;
52 | }
53 |
54 | public LocalDateTime getReceivedAt() {
55 | return receivedAt;
56 | }
57 |
58 | public void setReceivedAt(final LocalDateTime receivedAt) {
59 | this.receivedAt = receivedAt;
60 | }
61 |
62 | public boolean isArchived() {
63 | return archived;
64 | }
65 |
66 | public void setArchived(final boolean archived) {
67 | this.archived = archived;
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/dto/SurveillanceImageUpdateResource.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.dto; //NOSONAR
2 |
3 | /**
4 | * DTO used to update {@link com.github._1element.sc.domain.SurveillanceImage}.
5 | */
6 | public class SurveillanceImageUpdateResource {
7 |
8 | private long id;
9 |
10 | private boolean archived;
11 |
12 | public long getId() {
13 | return id;
14 | }
15 |
16 | public void setId(final long id) {
17 | this.id = id;
18 | }
19 |
20 | public boolean isArchived() {
21 | return archived;
22 | }
23 |
24 | public void setArchived(final boolean archived) {
25 | this.archived = archived;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/events/ImageReceivedEvent.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.events; //NOSONAR
2 |
3 | import com.github._1element.sc.domain.Camera;
4 |
5 | /**
6 | * Event for received image.
7 | */
8 | public class ImageReceivedEvent {
9 |
10 | private byte[] image;
11 |
12 | private Camera source;
13 |
14 | public ImageReceivedEvent(byte[] image, Camera source) {
15 | this.image = image;
16 | this.source = source;
17 | }
18 |
19 | public byte[] getImage() {
20 | return image;
21 | }
22 |
23 | public Camera getSource() {
24 | return source;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/events/PushNotificationEvent.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.events; //NOSONAR
2 |
3 | import com.github._1element.sc.domain.Camera;
4 |
5 | /**
6 | * Event to trigger push notification.
7 | */
8 | public class PushNotificationEvent {
9 |
10 | private Camera camera;
11 |
12 | public PushNotificationEvent(Camera camera) {
13 | this.camera = camera;
14 | }
15 |
16 | public Camera getCamera() {
17 | return camera;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/events/RemoteCopyEvent.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.events; //NOSONAR
2 |
3 | /**
4 | * Event to trigger remote copy.
5 | */
6 | public class RemoteCopyEvent {
7 |
8 | private String fileName;
9 |
10 | public RemoteCopyEvent(String fileName) {
11 | this.fileName = fileName;
12 | }
13 |
14 | public String getFileName() {
15 | return fileName;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/CameraNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(value = HttpStatus.NOT_FOUND)
7 | public class CameraNotFoundException extends Exception {
8 |
9 | private static final String MESSAGE_CAMERA_NOT_FOUND = "Camera not found.";
10 |
11 | public CameraNotFoundException() {
12 | super(MESSAGE_CAMERA_NOT_FOUND);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/FTPRemoteCopyException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | public class FTPRemoteCopyException extends Exception {
4 |
5 | public FTPRemoteCopyException(String message) {
6 | super(message);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/ForbiddenException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(HttpStatus.FORBIDDEN)
7 | public class ForbiddenException extends Exception {
8 |
9 | public ForbiddenException(String message) {
10 | super(message);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/PropertyNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | public class PropertyNotFoundException extends Exception {
4 |
5 | public PropertyNotFoundException(String message) {
6 | super(message);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/ProxyException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
7 | public class ProxyException extends Exception {
8 |
9 | public ProxyException(Throwable cause) {
10 | super(cause);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/PushNotificationClientException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | public class PushNotificationClientException extends Exception {
4 |
5 | private static final String MESSAGE = "Push notification could not be delivered to external service.";
6 |
7 | public PushNotificationClientException(Throwable cause) {
8 | super(MESSAGE, cause);
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/ResourceNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(value = HttpStatus.NOT_FOUND)
7 | public class ResourceNotFoundException extends Exception {
8 |
9 | public ResourceNotFoundException(String message) {
10 | super(message);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/exception/SFTPRemoteCopyException.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.exception; //NOSONAR
2 |
3 | public class SFTPRemoteCopyException extends Exception {
4 |
5 | public SFTPRemoteCopyException(String message) {
6 | super(message);
7 | }
8 |
9 | public SFTPRemoteCopyException(String message, Throwable cause) {
10 | super(message, cause);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/FTPRemoteCopyProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.apache.commons.io.FileUtils;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.stereotype.Component;
6 |
7 | /**
8 | * Configuration properties for FTP remote copy.
9 | */
10 | @Component
11 | @ConfigurationProperties("sc.remotecopy.ftp")
12 | public class FTPRemoteCopyProperties {
13 |
14 | private boolean enabled;
15 |
16 | private String host;
17 |
18 | private String dir = "/";
19 |
20 | private String username;
21 |
22 | private String password;
23 |
24 | private boolean cleanupEnabled = false;
25 |
26 | private long cleanupMaxDiskSpace;
27 |
28 | private int cleanupKeep;
29 |
30 | public boolean isEnabled() {
31 | return enabled;
32 | }
33 |
34 | public void setEnabled(final boolean enabled) {
35 | this.enabled = enabled;
36 | }
37 |
38 | public String getHost() {
39 | return host;
40 | }
41 |
42 | public void setHost(final String host) {
43 | this.host = host;
44 | }
45 |
46 | public String getDir() {
47 | return dir;
48 | }
49 |
50 | public void setDir(final String dir) {
51 | this.dir = dir;
52 | }
53 |
54 | public String getUsername() {
55 | return username;
56 | }
57 |
58 | public void setUsername(final String username) {
59 | this.username = username;
60 | }
61 |
62 | public String getPassword() {
63 | return password;
64 | }
65 |
66 | public void setPassword(final String password) {
67 | this.password = password;
68 | }
69 |
70 | public boolean isCleanupEnabled() {
71 | return cleanupEnabled;
72 | }
73 |
74 | public void setCleanupEnabled(final boolean cleanupEnabled) {
75 | this.cleanupEnabled = cleanupEnabled;
76 | }
77 |
78 | public long getCleanupMaxDiskSpace() {
79 | return cleanupMaxDiskSpace;
80 | }
81 |
82 | public void setCleanupMaxDiskSpace(final long cleanupMaxDiskSpace) {
83 | this.cleanupMaxDiskSpace = FileUtils.ONE_MB * cleanupMaxDiskSpace;
84 | }
85 |
86 | public int getCleanupKeep() {
87 | return cleanupKeep;
88 | }
89 |
90 | public void setCleanupKeep(final int cleanupKeep) {
91 | this.cleanupKeep = cleanupKeep;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/FtpProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * FTP server specific configuration properties.
8 | */
9 | @Component
10 | @ConfigurationProperties("sc.ftp")
11 | public class FtpProperties {
12 |
13 | private boolean enabled = false;
14 |
15 | private int port = 2121;
16 |
17 | public boolean isEnabled() {
18 | return enabled;
19 | }
20 |
21 | public void setEnabled(final boolean enabled) {
22 | this.enabled = enabled;
23 | }
24 |
25 | public int getPort() {
26 | return port;
27 | }
28 |
29 | public void setPort(final int port) {
30 | this.port = port;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/ImageProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * Image specific configuration properties.
8 | */
9 | @Component
10 | @ConfigurationProperties("sc.image")
11 | public class ImageProperties {
12 |
13 | private String storageDir;
14 |
15 | private String[] validExtensions;
16 |
17 | private int pageSize = 100;
18 |
19 | public String getStorageDir() {
20 | return storageDir;
21 | }
22 |
23 | public void setStorageDir(final String storageDir) {
24 | this.storageDir = storageDir;
25 | }
26 |
27 | public String[] getValidExtensions() {
28 | return validExtensions;
29 | }
30 |
31 | public void setValidExtensions(final String[] validExtensions) {
32 | this.validExtensions = validExtensions;
33 | }
34 |
35 | public int getPageSize() {
36 | return pageSize;
37 | }
38 |
39 | public void setPageSize(final int pageSize) {
40 | this.pageSize = pageSize;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/ImageThumbnailProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * Image thumbnail specific configuration properties.
8 | */
9 | @Component
10 | @ConfigurationProperties("sc.image.thumbnail")
11 | public class ImageThumbnailProperties {
12 |
13 | private int width = 200;
14 |
15 | private int height = 200;
16 |
17 | private double quality = 0.8;
18 |
19 | public int getWidth() {
20 | return width;
21 | }
22 |
23 | public void setWidth(final int width) {
24 | this.width = width;
25 | }
26 |
27 | public int getHeight() {
28 | return height;
29 | }
30 |
31 | public void setHeight(final int height) {
32 | this.height = height;
33 | }
34 |
35 | public double getQuality() {
36 | return quality;
37 | }
38 |
39 | public void setQuality(final double quality) {
40 | this.quality = quality;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/MqttProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * MQTT specific configuration properties.
8 | */
9 | @Component
10 | @ConfigurationProperties("sc.mqtt")
11 | public class MqttProperties {
12 |
13 | private boolean enabled = false;
14 |
15 | private String brokerConnection;
16 |
17 | private String topicFilter;
18 |
19 | private String username;
20 |
21 | private String password;
22 |
23 | public boolean isEnabled() {
24 | return enabled;
25 | }
26 |
27 | public void setEnabled(final boolean enabled) {
28 | this.enabled = enabled;
29 | }
30 |
31 | public String getBrokerConnection() {
32 | return brokerConnection;
33 | }
34 |
35 | public void setBrokerConnection(final String brokerConnection) {
36 | this.brokerConnection = brokerConnection;
37 | }
38 |
39 | public String getTopicFilter() {
40 | return topicFilter;
41 | }
42 |
43 | public void setTopicFilter(final String topicFilter) {
44 | this.topicFilter = topicFilter;
45 | }
46 |
47 | public String getUsername() {
48 | return username;
49 | }
50 |
51 | public void setUsername(final String username) {
52 | this.username = username;
53 | }
54 |
55 | public String getPassword() {
56 | return password;
57 | }
58 |
59 | public void setPassword(final String password) {
60 | this.password = password;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/MultiCameraAwareProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import com.github._1element.sc.exception.PropertyNotFoundException;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.core.env.Environment;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * Properties handling for multi camera.
11 | */
12 | @Component
13 | public class MultiCameraAwareProperties {
14 |
15 | private final Environment environment;
16 |
17 | public static final String PROPERTY_MULTI_CAMERA_PREFIX = "sc.camera[%s].";
18 |
19 | @Autowired
20 | public MultiCameraAwareProperties(final Environment environment) {
21 | this.environment = environment;
22 | }
23 |
24 | /**
25 | * Returns property value for the provided key and camera id.
26 | *
27 | * @param propertyKey the property key to resolve
28 | * @param cameraId the camera id to use for the property key building
29 | * @param targetType the expected type of the property value
30 | *
31 | * @return property value
32 | * @throws PropertyNotFoundException exception if property was not found
33 | */
34 | public T getProperty(final String propertyKey, final String cameraId, final Class targetType)
35 | throws PropertyNotFoundException {
36 | if (StringUtils.isBlank(cameraId)) {
37 | throw new PropertyNotFoundException(String.format("Property '%s' not found. Empty camera id was given.",
38 | propertyKey));
39 | }
40 |
41 | final String formattedPropertyKey = String.format(propertyKey, cameraId);
42 |
43 | if (!environment.containsProperty(formattedPropertyKey)) {
44 | throw new PropertyNotFoundException(String.format("Property not found for key '%s'.", formattedPropertyKey));
45 | }
46 |
47 | return environment.getProperty(formattedPropertyKey, targetType);
48 | }
49 |
50 | /**
51 | * Returns property value for the provided key and camera id. Default value if none found.
52 | *
53 | * @param propertyKey the property key to resolve
54 | * @param cameraId the camera id to use for the property key building
55 | * @param targetType the expected type of the property value
56 | * @param defaultValue the default value if none found
57 | *
58 | * @return property value
59 | */
60 | public T getProperty(final String propertyKey, final String cameraId, final Class targetType,
61 | final T defaultValue) {
62 | try {
63 | return getProperty(propertyKey, cameraId, targetType);
64 | } catch (final Exception exception) {
65 | return defaultValue;
66 | }
67 | }
68 |
69 | /**
70 | * Returns string property value for given key and camera id.
71 | *
72 | * @param propertyKey the property key to resolve
73 | * @param cameraId the camera id to use for the property key building
74 | *
75 | * @return property value
76 | * @throws PropertyNotFoundException exception if property was not found
77 | */
78 | public String getProperty(final String propertyKey, final String cameraId) throws PropertyNotFoundException {
79 | return getProperty(propertyKey, cameraId, String.class);
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/PushNotificationProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * Push notification properties.
8 | */
9 | @Component
10 | @ConfigurationProperties("sc.pushnotification")
11 | public class PushNotificationProperties {
12 |
13 | private boolean enabled = false;
14 |
15 | private String adapter;
16 |
17 | private String apiToken;
18 |
19 | private String userToken;
20 |
21 | private long groupTime = 0;
22 |
23 | private String url;
24 |
25 | public boolean isEnabled() {
26 | return enabled;
27 | }
28 |
29 | public void setEnabled(final boolean enabled) {
30 | this.enabled = enabled;
31 | }
32 |
33 | public String getAdapter() {
34 | return adapter;
35 | }
36 |
37 | public void setAdapter(final String adapter) {
38 | this.adapter = adapter;
39 | }
40 |
41 | public String getApiToken() {
42 | return apiToken;
43 | }
44 |
45 | public void setApiToken(final String apiToken) {
46 | this.apiToken = apiToken;
47 | }
48 |
49 | public String getUserToken() {
50 | return userToken;
51 | }
52 |
53 | public void setUserToken(final String userToken) {
54 | this.userToken = userToken;
55 | }
56 |
57 | public long getGroupTime() {
58 | return groupTime;
59 | }
60 |
61 | public void setGroupTime(final long groupTime) {
62 | this.groupTime = groupTime;
63 | }
64 |
65 | public String getUrl() {
66 | return url;
67 | }
68 |
69 | public void setUrl(final String url) {
70 | this.url = url;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/SFTPRemoteCopyProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.apache.commons.io.FileUtils;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.stereotype.Component;
6 |
7 | /**
8 | * Configuration properties for SFTP remote copy.
9 | */
10 | @Component
11 | @ConfigurationProperties("sc.remotecopy.sftp")
12 | public class SFTPRemoteCopyProperties {
13 |
14 | private boolean enabled;
15 |
16 | private String host;
17 |
18 | private String dir = "/";
19 |
20 | private String username;
21 |
22 | private String password;
23 |
24 | private boolean cleanupEnabled = false;
25 |
26 | private long cleanupMaxDiskSpace;
27 |
28 | private int cleanupKeep;
29 |
30 | public boolean isEnabled() {
31 | return enabled;
32 | }
33 |
34 | public void setEnabled(final boolean enabled) {
35 | this.enabled = enabled;
36 | }
37 |
38 | public String getHost() {
39 | return host;
40 | }
41 |
42 | public void setHost(final String host) {
43 | this.host = host;
44 | }
45 |
46 | public String getDir() {
47 | return dir;
48 | }
49 |
50 | public void setDir(final String dir) {
51 | this.dir = dir;
52 | }
53 |
54 | public String getUsername() {
55 | return username;
56 | }
57 |
58 | public void setUsername(final String username) {
59 | this.username = username;
60 | }
61 |
62 | public String getPassword() {
63 | return password;
64 | }
65 |
66 | public void setPassword(final String password) {
67 | this.password = password;
68 | }
69 |
70 | public boolean isCleanupEnabled() {
71 | return cleanupEnabled;
72 | }
73 |
74 | public void setCleanupEnabled(final boolean cleanupEnabled) {
75 | this.cleanupEnabled = cleanupEnabled;
76 | }
77 |
78 | public long getCleanupMaxDiskSpace() {
79 | return cleanupMaxDiskSpace;
80 | }
81 |
82 | public void setCleanupMaxDiskSpace(final long cleanupMaxDiskSpace) {
83 | this.cleanupMaxDiskSpace = FileUtils.ONE_MB * cleanupMaxDiskSpace;
84 | }
85 |
86 | public int getCleanupKeep() {
87 | return cleanupKeep;
88 | }
89 |
90 | public void setCleanupKeep(final int cleanupKeep) {
91 | this.cleanupKeep = cleanupKeep;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/StreamGenerationProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | @Component
7 | @ConfigurationProperties("sc.stream-generation")
8 | public class StreamGenerationProperties {
9 |
10 | private int mjpegDelay = 500;
11 |
12 | public int getMjpegDelay() {
13 | return mjpegDelay;
14 | }
15 |
16 | public void setMjpegDelay(final int mjpegDelay) {
17 | this.mjpegDelay = mjpegDelay;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/properties/SurveillanceSecurityProperties.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.properties; //NOSONAR
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * Configuration properties for handling authorization.
8 | */
9 | @Component
10 | @ConfigurationProperties("sc.security")
11 | public class SurveillanceSecurityProperties {
12 |
13 | private String username;
14 |
15 | private String password;
16 |
17 | private String secret;
18 |
19 | private String cookieName = "JWT";
20 |
21 | private int tokenExpiration = 2592000;
22 |
23 | public String getUsername() {
24 | return username;
25 | }
26 |
27 | public void setUsername(final String username) {
28 | this.username = username;
29 | }
30 |
31 | public String getPassword() {
32 | return password;
33 | }
34 |
35 | public void setPassword(final String password) {
36 | this.password = password;
37 | }
38 |
39 | public String getSecret() {
40 | return secret;
41 | }
42 |
43 | public void setSecret(final String secret) {
44 | this.secret = secret;
45 | }
46 |
47 | public String getCookieName() {
48 | return cookieName;
49 | }
50 |
51 | public void setCookieName(final String cookieName) {
52 | this.cookieName = cookieName;
53 | }
54 |
55 | public int getTokenExpiration() {
56 | return tokenExpiration;
57 | }
58 |
59 | public void setTokenExpiration(final int tokenExpiration) {
60 | this.tokenExpiration = tokenExpiration;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/repository/PushNotificationSettingRepository.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.repository; //NOSONAR
2 |
3 | import com.github._1element.sc.domain.PushNotificationSetting;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | /**
7 | * Repository for push notification settings.
8 | */
9 | public interface PushNotificationSettingRepository extends JpaRepository {
10 |
11 | PushNotificationSetting findByCameraId(String cameraId);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/repository/SurveillanceImageRepository.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.repository; //NOSONAR
2 |
3 | import com.github._1element.sc.domain.SurveillanceImage;
4 | import org.springframework.data.domain.Page;
5 | import org.springframework.data.domain.Pageable;
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 | import org.springframework.data.jpa.repository.Modifying;
8 | import org.springframework.data.jpa.repository.Query;
9 | import org.springframework.data.repository.query.Param;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import java.time.LocalDateTime;
13 | import java.util.List;
14 |
15 | /**
16 | * Surveillance image repository.
17 | */
18 | public interface SurveillanceImageRepository extends JpaRepository {
19 |
20 | Page findAllByArchived(boolean archived, Pageable pageable);
21 |
22 | Page findAllByCameraIdAndArchived(String cameraId, boolean archived, Pageable pageable);
23 |
24 | @Query("select s from SurveillanceImage s where (s.receivedAt between :start and :end) and s.archived = :archived")
25 | Page findAllForDateRange(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end,
26 | @Param("archived") boolean archived, Pageable pageable);
27 |
28 | @Query("select s from SurveillanceImage s where s.cameraId = :cameraId and (s.receivedAt between :start and :end) "
29 | + "and s.archived = :archived")
30 | Page findAllForDateRangeAndCameraId(@Param("start") LocalDateTime start,
31 | @Param("end") LocalDateTime end,
32 | @Param("cameraId") String cameraId,
33 | @Param("archived") boolean archived, Pageable pageable);
34 |
35 | @Query("select count(*) from SurveillanceImage s where s.cameraId = :cameraId and s.archived = false")
36 | Long countImagesForCamera(@Param("cameraId") String cameraId);
37 |
38 | @Query("select s.receivedAt from SurveillanceImage s where s.cameraId = :cameraId and s.archived = false "
39 | + "order by s.receivedAt desc")
40 | List getMostRecentImageDateForCamera(@Param("cameraId") String cameraId, Pageable pageable);
41 |
42 | @Query("select s from SurveillanceImage s where s.archived = true and s.receivedAt <= :dateBefore")
43 | List getArchivedImagesToCleanup(@Param("dateBefore") LocalDateTime before);
44 |
45 | @Modifying
46 | @Transactional
47 | @Query("update SurveillanceImage s set s.archived = true where s.id in :ids")
48 | void updateSetArchived(@Param("ids") List ids);
49 |
50 | @Modifying
51 | @Transactional
52 | @Query("update SurveillanceImage s set s.archived = :value where s.receivedAt <= :dateBefore")
53 | void updateArchiveState(@Param("value") boolean value, @Param("dateBefore") LocalDateTime before);
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/security/JwtAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.security; //NOSONAR
2 |
3 | import org.springframework.security.core.AuthenticationException;
4 | import org.springframework.security.web.AuthenticationEntryPoint;
5 | import org.springframework.stereotype.Component;
6 |
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 | import java.io.IOException;
10 |
11 | /**
12 | * Authentication entry point.
13 | * This will respond to all unauthorized requests with a 401 header.
14 | */
15 | @Component
16 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
17 |
18 | @Override
19 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
20 | throws IOException {
21 |
22 | // This is invoked when a user tries to access a secured REST resource without supplying any credentials.
23 | // We just send a 401 unauthorized response.
24 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/security/JwtAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.security; //NOSONAR
2 |
3 | import com.github._1element.sc.service.JwtAuthenticationService;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.security.core.Authentication;
6 | import org.springframework.security.core.context.SecurityContextHolder;
7 | import org.springframework.web.filter.OncePerRequestFilter;
8 |
9 | import javax.servlet.FilterChain;
10 | import javax.servlet.ServletException;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 | import java.io.IOException;
14 |
15 | /**
16 | * Authentication filter, will be passed through for all requests.
17 | * If cookie with valid token exists authentication will be passed to the security context holder.
18 | */
19 | public class JwtAuthenticationFilter extends OncePerRequestFilter {
20 |
21 | @Autowired
22 | private JwtAuthenticationService jwtAuthenticationService;
23 |
24 | @Override
25 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
26 | throws IOException, ServletException {
27 |
28 | // gets valid authentication if cookie with token (JWT) exists
29 | Authentication authentication = jwtAuthenticationService.getAuthentication(request);
30 | SecurityContextHolder.getContext().setAuthentication(authentication);
31 |
32 | chain.doFilter(request, response);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/security/JwtAuthenticationRequest.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.security; //NOSONAR
2 |
3 | /**
4 | * Authentication request with username and password.
5 | */
6 | public class JwtAuthenticationRequest {
7 |
8 | private String username;
9 | private String password;
10 |
11 | protected JwtAuthenticationRequest() {
12 | }
13 |
14 | public JwtAuthenticationRequest(String username, String password) {
15 | this.username = username;
16 | this.password = password;
17 | }
18 |
19 | public String getUsername() {
20 | return this.username;
21 | }
22 |
23 | public String getPassword() {
24 | return this.password;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/service/CleanupTasks.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.service; //NOSONAR
2 |
3 | import com.github._1element.sc.domain.SurveillanceImage;
4 | import com.github._1element.sc.properties.ImageProperties;
5 | import com.github._1element.sc.repository.SurveillanceImageRepository;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.beans.factory.annotation.Value;
10 | import org.springframework.scheduling.annotation.Scheduled;
11 | import org.springframework.stereotype.Component;
12 |
13 | import java.io.IOException;
14 | import java.nio.file.Path;
15 | import java.time.LocalDateTime;
16 | import java.util.List;
17 |
18 | /**
19 | * Scheduler component to perform clean up tasks.
20 | */
21 | @Component
22 | public class CleanupTasks {
23 |
24 | private final SurveillanceImageRepository imageRepository;
25 |
26 | private final FileService fileService;
27 |
28 | private final ImageProperties imageProperties;
29 |
30 | @Value("${sc.archive.cleanup.enabled:false}")
31 | private Boolean isCleanupEnabled;
32 |
33 | @Value("${sc.archive.cleanup.keep:72}")
34 | private Integer keepHours;
35 |
36 | private static final String THUMBNAIL_PREFIX = "thumbnail.";
37 |
38 | private static final String CRON_EVERY_DAY_AT_4_AM = "0 0 4 * * *";
39 |
40 | private static final Logger LOG = LoggerFactory.getLogger(CleanupTasks.class);
41 |
42 | /**
43 | * Constructor.
44 | *
45 | * @param imageRepository the surveillance image repository
46 | * @param fileService the file service dependency
47 | * @param imageProperties the configured image properties
48 | */
49 | @Autowired
50 | public CleanupTasks(final SurveillanceImageRepository imageRepository, final FileService fileService,
51 | final ImageProperties imageProperties) {
52 | this.imageRepository = imageRepository;
53 | this.fileService = fileService;
54 | this.imageProperties = imageProperties;
55 | }
56 |
57 | /**
58 | * Remove archived images older than X hours.
59 | */
60 | @Scheduled(cron = CRON_EVERY_DAY_AT_4_AM)
61 | public void cleanupArchive() {
62 | if (!Boolean.TRUE.equals(isCleanupEnabled)) {
63 | LOG.info("Task to remove old archived images not enabled in configuration. Do nothing.");
64 | return;
65 | }
66 |
67 | final LocalDateTime removeBefore = LocalDateTime.now().minusHours(keepHours);
68 | final List images = imageRepository.getArchivedImagesToCleanup(removeBefore);
69 |
70 | int numberOfImages = 0;
71 | for (final SurveillanceImage image : images) {
72 | final Path imageFilePath = fileService.getPath(imageProperties.getStorageDir() + image.getFileName());
73 | final Path thumbnailFilePath = fileService.getPath(imageProperties.getStorageDir() + THUMBNAIL_PREFIX
74 | + image.getFileName());
75 | try {
76 | fileService.delete(imageFilePath);
77 | fileService.delete(thumbnailFilePath);
78 | numberOfImages++;
79 | } catch (final IOException exception) {
80 | LOG.warn("Exception occurred while removing old archived image/thumbnail '{}'/'{}', cause '{}'",
81 | imageFilePath.toString(), thumbnailFilePath.toString(), exception.getMessage());
82 | }
83 | imageRepository.delete(image);
84 | }
85 |
86 | LOG.info("Successfully removed {} archived images.", numberOfImages);
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/service/JwtUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.service; //NOSONAR
2 |
3 | import com.github._1element.sc.properties.SurveillanceSecurityProperties;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.security.core.userdetails.User;
6 | import org.springframework.security.core.userdetails.UserDetails;
7 | import org.springframework.security.core.userdetails.UserDetailsService;
8 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.util.Objects;
12 |
13 | /**
14 | * User details service class.
15 | */
16 | @Service
17 | public class JwtUserDetailsService implements UserDetailsService {
18 |
19 | private final SurveillanceSecurityProperties securityProperties;
20 |
21 | @Autowired
22 | public JwtUserDetailsService(final SurveillanceSecurityProperties securityProperties) {
23 | this.securityProperties = securityProperties;
24 | }
25 |
26 | /**
27 | * Locates the user based on the username.
28 | * There is currently only one single user defined in the application.properties file.
29 | *
30 | * @param username the username to load
31 | * @return a fully populated user record
32 | * @throws UsernameNotFoundException if user was not found
33 | */
34 | @Override
35 | public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
36 | Objects.requireNonNull(securityProperties.getUsername(), "Username must not be null. Check your configuration.");
37 | Objects.requireNonNull(securityProperties.getPassword(), "Password must not be null. Check your configuration.");
38 | Objects.requireNonNull(username, "Provided username must not be null.");
39 |
40 | if (securityProperties.getUsername().equals(username)) {
41 | return User.withUsername(securityProperties.getUsername()).password(securityProperties.getPassword())
42 | .roles("USER").build();
43 | }
44 |
45 | throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/service/MjpegGenerationService.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.service; //NOSONAR
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import javax.servlet.http.HttpServletResponse;
8 |
9 | import com.github._1element.sc.properties.StreamGenerationProperties;
10 | import com.github._1element.sc.utils.RestTemplateUtils;
11 | import org.springframework.boot.web.client.RestTemplateBuilder;
12 | import org.springframework.stereotype.Service;
13 | import org.springframework.web.client.RestClientException;
14 | import org.springframework.web.client.RestTemplate;
15 |
16 | /**
17 | * Generation service to create MJPEG streams.
18 | */
19 | @Service
20 | public class MjpegGenerationService {
21 |
22 | private final StreamGenerationProperties streamGenerationProperties;
23 |
24 | private final RestTemplateBuilder restTemplateBuilder;
25 |
26 | private static final String NL = "\r\n";
27 |
28 | private static final String MJPEG_BOUNDARY = "--BoundaryString";
29 |
30 | private static final String MJPEG_HEAD = MJPEG_BOUNDARY + NL + "Content-type: image/jpeg" + NL + "Content-Length: ";
31 |
32 | public MjpegGenerationService(final StreamGenerationProperties streamGenerationProperties,
33 | final RestTemplateBuilder restTemplateBuilder) {
34 | this.streamGenerationProperties = streamGenerationProperties;
35 | this.restTemplateBuilder = restTemplateBuilder;
36 | }
37 |
38 | /**
39 | * Continuously retrieve JPEG image from given camera snapshotUrl and
40 | * output result as MJPEG stream to the provided HttpServlet response.
41 | *
42 | * @param snapshotUrl the camera snapshot URL to retrieve image from
43 | * @param response the HTTP response to write to
44 | * @throws IOException if output stream could not be written (e.g. client disconnects)
45 | * @throws RestClientException if an HTTP error occurred while accessing the snapshot URL
46 | */
47 | public void writeSnapshotToOutputStream(final String snapshotUrl, final HttpServletResponse response)
48 | throws IOException {
49 | final RestTemplate restTemplate = RestTemplateUtils.buildWithAuth(restTemplateBuilder, snapshotUrl);
50 |
51 | final OutputStream outputStream = response.getOutputStream();
52 |
53 | while (!Thread.currentThread().isInterrupted()) {
54 | final byte[] imageData = restTemplate.getForObject(snapshotUrl, byte[].class);
55 |
56 | outputStream.write((MJPEG_HEAD + imageData.length + NL + NL).getBytes());
57 | outputStream.write(imageData);
58 | outputStream.write((NL + NL).getBytes());
59 | outputStream.flush();
60 |
61 | try {
62 | TimeUnit.MILLISECONDS.sleep(streamGenerationProperties.getMjpegDelay());
63 | } catch (final InterruptedException exception) {
64 | Thread.currentThread().interrupt();
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * Set response content type.
71 | *
72 | * @param response HTTP response to set header
73 | */
74 | public void setContentType(final HttpServletResponse response) {
75 | response.setContentType("multipart/x-mixed-replace; boundary=" + MJPEG_BOUNDARY);
76 | }
77 |
78 | /**
79 | * Set response cache control header.
80 | *
81 | * @param response HTTP response to set header
82 | */
83 | public void setCacheControlHeader(final HttpServletResponse response) {
84 | response.setHeader("Cache-Control", "no-cache, private");
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/service/ModelMappingService.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.service; //NOSONAR
2 |
3 | import com.github._1element.sc.controller.SurveillanceProxyController;
4 | import com.github._1element.sc.controller.SurveillanceStreamGenerationController;
5 | import com.github._1element.sc.domain.Camera;
6 | import com.github._1element.sc.domain.SurveillanceImage;
7 | import com.github._1element.sc.dto.CameraResource;
8 | import com.github._1element.sc.dto.SurveillanceImageResource;
9 | import com.github._1element.sc.exception.CameraNotFoundException;
10 | import com.github._1element.sc.exception.ProxyException;
11 | import com.github._1element.sc.repository.CameraRepository;
12 | import com.github._1element.sc.utils.URIConstants;
13 | import org.modelmapper.ModelMapper;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 | import org.springframework.beans.factory.annotation.Autowired;
17 | import org.springframework.hateoas.mvc.ControllerLinkBuilder;
18 | import org.springframework.stereotype.Service;
19 |
20 | /**
21 | * Service to convert between internal entities and data transfer objects (DTOs).
22 | * This way we do not expose our internal domain objects/entities.
23 | */
24 | @Service
25 | public class ModelMappingService {
26 |
27 | private final ModelMapper modelMapper;
28 |
29 | private final CameraRepository cameraRepository;
30 |
31 | private static final Logger LOG = LoggerFactory.getLogger(ModelMappingService.class);
32 |
33 | @Autowired
34 | public ModelMappingService(final ModelMapper modelMapper, final CameraRepository cameraRepository) {
35 | this.modelMapper = modelMapper;
36 | this.cameraRepository = cameraRepository;
37 | }
38 |
39 | /**
40 | * Converts the provided {@link Camera} to a {@link CameraResource} with additional attributes.
41 | *
42 | * @param camera the camera to convert
43 | * @return converted camera resource
44 | */
45 | public CameraResource convertCameraToResource(final Camera camera) {
46 | final CameraResource cameraResource = modelMapper.map(camera, CameraResource.class);
47 |
48 | String snapshotProxyUrl = null;
49 | try {
50 | snapshotProxyUrl = ControllerLinkBuilder.linkTo(ControllerLinkBuilder
51 | .methodOn(SurveillanceProxyController.class).retrieveSnapshot(camera.getId())).toString();
52 | } catch (CameraNotFoundException | ProxyException exception) {
53 | LOG.debug("Exception occurred during link building: '{}'", exception.getMessage());
54 | }
55 | // methodOn() does not work because of void return type
56 | final String streamGeneratorUrl = ControllerLinkBuilder.linkTo(SurveillanceStreamGenerationController.class)
57 | .slash(URIConstants.GENERATE_MJPEG.replace("{id}", camera.getId())).toString();
58 |
59 | cameraResource.setSnapshotProxyUrl(snapshotProxyUrl);
60 | cameraResource.setStreamGeneratorUrl(streamGeneratorUrl);
61 |
62 | return cameraResource;
63 | }
64 |
65 | /**
66 | * Converts the provided {@link SurveillanceImage} to a {@link SurveillanceImageResource}.
67 | *
68 | * @param surveillanceImage the surveillance image to convert
69 | * @return converted surveillance image resource
70 | */
71 | public SurveillanceImageResource convertSurveillanceImageToResource(final SurveillanceImage surveillanceImage) {
72 | final SurveillanceImageResource surveillanceImageResource =
73 | modelMapper.map(surveillanceImage, SurveillanceImageResource.class);
74 |
75 | // add camera name
76 | final Camera camera = cameraRepository.findById(surveillanceImage.getCameraId());
77 | if (camera != null) {
78 | surveillanceImageResource.setCameraName(camera.getName());
79 | }
80 |
81 | return surveillanceImageResource;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/service/SurveillanceProxyService.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.service; //NOSONAR
2 |
3 | import com.github._1element.sc.exception.ProxyException;
4 | import com.github._1element.sc.utils.RestTemplateUtils;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.web.client.RestTemplateBuilder;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.stereotype.Service;
11 | import org.springframework.web.client.RestClientException;
12 | import org.springframework.web.client.RestTemplate;
13 |
14 | /**
15 | * Simple proxy service class.
16 | */
17 | @Service
18 | public class SurveillanceProxyService {
19 |
20 | private final RestTemplateBuilder restTemplateBuilder;
21 |
22 | private static final Logger LOG = LoggerFactory.getLogger(SurveillanceProxyService.class);
23 |
24 | @Autowired
25 | public SurveillanceProxyService(final RestTemplateBuilder restTemplateBuilder) {
26 | this.restTemplateBuilder = restTemplateBuilder;
27 | }
28 |
29 | /**
30 | * Retrieve the provided url (snapshot).
31 | *
32 | * @param url the url to retrieve
33 | * @return the image response
34 | */
35 | public ResponseEntity retrieveImage(final String url) throws ProxyException {
36 | final RestTemplate restTemplate = RestTemplateUtils.buildWithAuth(restTemplateBuilder, url);
37 |
38 | ResponseEntity response = null;
39 | try {
40 | response = restTemplate.getForEntity(url, byte[].class);
41 | } catch (final RestClientException exception) {
42 | LOG.debug("Could not retrieve snapshot for '{}': '{}'", url, exception.getMessage());
43 | throw new ProxyException(exception);
44 | }
45 |
46 | return response;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/service/ThumbnailService.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.service; //NOSONAR
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Path;
5 |
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Service;
10 |
11 | import com.github._1element.sc.properties.ImageThumbnailProperties;
12 |
13 | import net.coobird.thumbnailator.Thumbnails;
14 | import net.coobird.thumbnailator.name.Rename;
15 |
16 | /**
17 | * Thumbnail service.
18 | */
19 | @Service
20 | public class ThumbnailService {
21 |
22 | private final ImageThumbnailProperties imageThumbnailProperties;
23 |
24 | private static final Logger LOG = LoggerFactory.getLogger(ThumbnailService.class);
25 |
26 | @Autowired
27 | public ThumbnailService(final ImageThumbnailProperties imageThumbnailProperties) {
28 | this.imageThumbnailProperties = imageThumbnailProperties;
29 | }
30 |
31 | /**
32 | * Generates a thumbnail for the given path.
33 | *
34 | * @param path the file path to create a thumbnail for
35 | */
36 | public void createThumbnail(final Path path) {
37 | try {
38 | Thumbnails.of(path.toFile())
39 | .size(imageThumbnailProperties.getWidth(), imageThumbnailProperties.getHeight())
40 | .outputQuality(imageThumbnailProperties.getQuality())
41 | .toFiles(Rename.PREFIX_DOT_THUMBNAIL);
42 | } catch (final IOException exception) {
43 | LOG.warn("Unable to generate thumbnail: {}", exception.getMessage());
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/utils/RestTemplateUtils.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.utils; //NOSONAR
2 |
3 | import com.google.common.base.Splitter;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.boot.web.client.RestTemplateBuilder;
7 | import org.springframework.web.client.RestTemplate;
8 | import org.springframework.web.util.UriComponents;
9 | import org.springframework.web.util.UriComponentsBuilder;
10 |
11 | import java.util.List;
12 |
13 | public final class RestTemplateUtils {
14 |
15 | private static final Logger LOG = LoggerFactory.getLogger(RestTemplateUtils.class);
16 |
17 | private RestTemplateUtils() {
18 | // hide constructor for static utility class
19 | }
20 |
21 | /**
22 | * Builds a RestTemplate with authorization for the provided URL using the given RestTemplateBuilder.
23 | * Authorization credentials will be extracted from the URL (http://username:password@host.example/).
24 | *
25 | * @param restTemplateBuilder the rest template builder to use
26 | * @param url the url to use
27 | * @return rest template
28 | */
29 | public static RestTemplate buildWithAuth(RestTemplateBuilder restTemplateBuilder, String url) {
30 | UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(url).build();
31 | String userInfo = uriComponents.getUserInfo();
32 | if (userInfo != null) {
33 | List auth = Splitter.on(":").splitToList(userInfo);
34 | if (auth.size() == 2) {
35 | return restTemplateBuilder.basicAuthorization(auth.get(0), auth.get(1)).build();
36 | } else {
37 | LOG.warn("Could not extract username and password: '{}'", userInfo);
38 | }
39 | }
40 |
41 | return restTemplateBuilder.build();
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/github/_1element/sc/utils/URIConstants.java:
--------------------------------------------------------------------------------
1 | package com.github._1element.sc.utils; //NOSONAR
2 |
3 | /**
4 | * Constants for URI building.
5 | */
6 | public final class URIConstants {
7 |
8 | // API
9 | public static final String API_ROOT = "/api/v1";
10 |
11 | public static final String API_RECORDINGS = "/recordings";
12 |
13 | public static final String API_CAMERAS = "/cameras";
14 |
15 | public static final String API_PUSH_NOTIFICATION_SETTINGS = "/push-notification-settings";
16 |
17 | public static final String API_PROPERTIES = "/properties";
18 |
19 | public static final String API_AUTH = "/auth";
20 |
21 | // Generation
22 | public static final String GENERATE_ROOT = "/generate";
23 |
24 | public static final String GENERATE_MJPEG = "/mjpeg/{id}";
25 |
26 | // Proxy
27 | public static final String PROXY_ROOT = "/proxy";
28 |
29 | public static final String PROXY_SNAPSHOT = "/snapshot/{id}";
30 |
31 | // Feed
32 | public static final String FEED_ROOT = "/feed";
33 |
34 | public static final String FEED_CAMERAS = "/cameras";
35 |
36 | private URIConstants() {
37 | // hide constructor
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/resources/messages.properties:
--------------------------------------------------------------------------------
1 | feed.status.title=Surveillance Center Status
2 | feed.status.date=Date
3 | healthcheck.title=Camera health check
4 | healthcheck.message=Camera {0} on host {1} is {2}
5 | push-notification.title=Camera {0}
6 | push-notification.message=Motion detected on camera {0} at timestamp: {1}.
7 |
--------------------------------------------------------------------------------
/src/main/resources/messages_de.properties:
--------------------------------------------------------------------------------
1 | feed.status.title=Surveillance Center Status
2 | feed.status.date=Status vom
3 | healthcheck.title=Kamera Erreichbarkeitsprüfung
4 | healthcheck.message=Kamera {0} (Host {1}) ist: {2}
5 | push-notification.title=Kamera {0}
6 | push-notification.message=Die Kamera {0} hat Bewegung erkannt und aufgezeichnet. Zeitstempel: {1}.
7 |
--------------------------------------------------------------------------------
/src/main/resources/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Oops an error occurred
9 |
10 |
32 |
33 |
34 |
35 |
404
36 |
Not found
37 |
38 | There seems to be a problem with the page you requested: /error.html
39 | The page was either not found or does not exist.
40 |