First commit
This commit is contained in:
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/build/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
.mvn
|
||||||
|
**/node_modules
|
||||||
84
pom.xml
Normal file
84
pom.xml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.minager</groupId>
|
||||||
|
<artifactId>minager</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Minager</name>
|
||||||
|
<description>Minecraft server managing application</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.0.0.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.mindrot/jbcrypt -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mindrot</groupId>
|
||||||
|
<artifactId>jbcrypt</artifactId>
|
||||||
|
<version>0.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency> -->
|
||||||
|
<!-- <groupId>commons-lang</groupId> -->
|
||||||
|
<!-- <artifactId>commons-lang</artifactId> -->
|
||||||
|
<!-- <version>2.3</version> -->
|
||||||
|
<!-- </dependency> -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
|
||||||
|
<!-- <dependency> -->
|
||||||
|
<!-- <groupId>org.apache.commons</groupId> -->
|
||||||
|
<!-- <artifactId>commons-lang3</artifactId> -->
|
||||||
|
<!-- <version>3.7</version> -->
|
||||||
|
<!-- </dependency> -->
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<executable>true</executable>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
28
src/main/bash/minager-service
Normal file
28
src/main/bash/minager-service
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
USER=minecraft
|
||||||
|
MINECRAFT_SERVER_PATH=/home/minecraft/minager/
|
||||||
|
MINECRAFT_SHELL=minager.sh
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
'start')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL start"
|
||||||
|
;;
|
||||||
|
'stop')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL stop"
|
||||||
|
;;
|
||||||
|
'status')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL status"
|
||||||
|
;;
|
||||||
|
'restart')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL restart"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# If no argument, we launch the app in case of server startup
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL start &>/dev/null"
|
||||||
|
# And show the script usage
|
||||||
|
echo "Usage: /etc/init.d/minecraft {start|stop|status|restart}\n" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
80
src/main/bash/minager.sh
Normal file
80
src/main/bash/minager.sh
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# kFreeBSD do not accept scripts as interpreters, using #!/bin/sh and sourcing.
|
||||||
|
if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then
|
||||||
|
set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
DESC="The Minager web app server"
|
||||||
|
NAME='minager'
|
||||||
|
MINECRAFT_HOME='/home/minecraft'
|
||||||
|
MINAGER_HOME="$MINECRAFT_HOME/minager"
|
||||||
|
DAEMON_HOME="$MINAGER_HOME/bin"
|
||||||
|
DAEMON="$DAEMON_HOME/$NAME.jar"
|
||||||
|
PIDFILE="$DAEMON_HOME/$NAME.pid"
|
||||||
|
SCRIPTNAME="$DAEMON_HOME/$0"
|
||||||
|
|
||||||
|
start()
|
||||||
|
{
|
||||||
|
# If PIDFILE exists and PID is in running processes
|
||||||
|
if [ -f $PIDFILE ] && kill -0 `cat $PIDFILE` 2>/dev/null
|
||||||
|
then
|
||||||
|
echo 'Service already running\n'
|
||||||
|
else
|
||||||
|
echo "Starting service $NAME"
|
||||||
|
cd $DAEMON_HOME
|
||||||
|
nohup 2>/dev/null java -jar $DAEMON &>/dev/null &
|
||||||
|
expr $! - 1 > $PIDFILE
|
||||||
|
echo "Service started [`cat $PIDFILE`]\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
# If PIDFILE doesn't exists or PID isn't in running processes
|
||||||
|
if [ ! -f "$PIDFILE" ] || ! kill -0 `cat "$PIDFILE"`
|
||||||
|
then
|
||||||
|
echo 'Service not running\n'
|
||||||
|
else
|
||||||
|
echo 'Stopping service...'
|
||||||
|
# Send signal to end to the process
|
||||||
|
kill -15 `cat "$PIDFILE"` && rm -f "$PIDFILE"
|
||||||
|
echo 'Service stopped\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status()
|
||||||
|
{
|
||||||
|
if [ -f $PIDFILE ] && kill -0 `cat $PIDFILE` 2>/dev/null
|
||||||
|
then
|
||||||
|
echo "Service is running (${GREEN}● active${NC})\n"
|
||||||
|
else
|
||||||
|
echo "Service not running (${RED}● inactive${NC})\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
'start')
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
'stop')
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
'status')
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
'restart')
|
||||||
|
stop
|
||||||
|
sleep 5
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $SCRIPTNAME {start|stop|status|restart}\n" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
28
src/main/bash/minecraft-server/minecraft-server-service
Normal file
28
src/main/bash/minecraft-server/minecraft-server-service
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
USER=takiguchi
|
||||||
|
MINECRAFT_SERVER_PATH=/home/minecraft/server
|
||||||
|
MINECRAFT_SHELL=minecraft-server.sh
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
'start')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL start"
|
||||||
|
;;
|
||||||
|
'stop')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL stop"
|
||||||
|
;;
|
||||||
|
'status')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL status"
|
||||||
|
;;
|
||||||
|
'restart')
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL restart"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# If no argument, we launch the app in case of server startup
|
||||||
|
sudo -H -u $USER bash -c "$MINECRAFT_SERVER_PATH/$MINECRAFT_SHELL start &>/dev/null"
|
||||||
|
# And show the script usage
|
||||||
|
echo "Usage: /etc/init.d/minecraft {start|stop|status|restart}\n" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
158
src/main/bash/minecraft-server/minecraft-server.sh
Normal file
158
src/main/bash/minecraft-server/minecraft-server.sh
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# kFreeBSD do not accept scripts as interpreters, using #!/bin/sh and sourcing.
|
||||||
|
if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then
|
||||||
|
set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
DESC="The Minecraft server"
|
||||||
|
NAME='minecraft-server'
|
||||||
|
MINECRAFT_HOME='/home/minecraft'
|
||||||
|
DAEMON_HOME="$MINECRAFT_HOME/server"
|
||||||
|
DAEMON="$DAEMON_HOME/$NAME.jar"
|
||||||
|
PIDFILE="$DAEMON_HOME/$NAME.pid"
|
||||||
|
SCRIPTNAME="$DAEMON_HOME/$0"
|
||||||
|
|
||||||
|
# ***********************************************
|
||||||
|
# Normal functions for bash commands.
|
||||||
|
# ***********************************************
|
||||||
|
start()
|
||||||
|
{
|
||||||
|
# If PIDFILE exists and PID is in running processes
|
||||||
|
if [ -f $PIDFILE ] && kill -0 `cat $PIDFILE` 2>/dev/null
|
||||||
|
then
|
||||||
|
echo 'Service already running\n'
|
||||||
|
else
|
||||||
|
echo "Starting service $NAME"
|
||||||
|
cd $DAEMON_HOME
|
||||||
|
nohup 2>/dev/null java -jar $DAEMON &>/dev/null &
|
||||||
|
expr $! - 1 > $PIDFILE
|
||||||
|
echo "Service started [`cat $PIDFILE`]\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
# If PIDFILE doesn't exists or PID isn't in running processes
|
||||||
|
if [ ! -f "$PIDFILE" ] || ! kill -0 `cat "$PIDFILE"`
|
||||||
|
then
|
||||||
|
echo 'Service not running\n'
|
||||||
|
else
|
||||||
|
echo 'Stopping service...'
|
||||||
|
# Send signal to end to the process
|
||||||
|
kill -15 `cat "$PIDFILE"` && rm -f "$PIDFILE"
|
||||||
|
echo 'Service stopped\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status()
|
||||||
|
{
|
||||||
|
if [ -f $PIDFILE ] && kill -0 `cat $PIDFILE` 2>/dev/null
|
||||||
|
then
|
||||||
|
echo "Service is running (${GREEN}● active${NC})\n"
|
||||||
|
else
|
||||||
|
echo "Service not running (${RED}● inactive${NC})\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ***********************************************
|
||||||
|
# Commands used by Minager to drive the server.
|
||||||
|
# ***********************************************
|
||||||
|
api_check_error()
|
||||||
|
{
|
||||||
|
if [ $1 != 0 ]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
api_status()
|
||||||
|
{
|
||||||
|
if [ -f $PIDFILE ] && kill -0 `cat $PIDFILE` 2>/dev/null
|
||||||
|
then
|
||||||
|
echo 1 # Running
|
||||||
|
else
|
||||||
|
echo 0 # Stopped
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
api_start() {
|
||||||
|
# If PIDFILE exists and PID is in running processes
|
||||||
|
if [ -f $PIDFILE ] && kill -0 `cat $PIDFILE` 2>/dev/null
|
||||||
|
then
|
||||||
|
exit 2 # STATE_UNCHANGED
|
||||||
|
else
|
||||||
|
cd $DAEMON_HOME
|
||||||
|
api_check_error $?
|
||||||
|
|
||||||
|
nohup 2>/dev/null java -jar $DAEMON &>/dev/null &
|
||||||
|
api_check_error $?
|
||||||
|
|
||||||
|
expr $! - 1 > $PIDFILE
|
||||||
|
api_check_error $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
api_stop()
|
||||||
|
{
|
||||||
|
# If PIDFILE doesn't exists or PID isn't in running processes
|
||||||
|
if [ ! -f "$PIDFILE" ] || ! kill -0 `cat "$PIDFILE"`
|
||||||
|
then
|
||||||
|
exit 2 # STATE_UNCHANGED
|
||||||
|
else
|
||||||
|
# Send signal to end to the process
|
||||||
|
kill -15 `cat "$PIDFILE"` && rm -f "$PIDFILE"
|
||||||
|
api_check_error $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
api_restart()
|
||||||
|
{
|
||||||
|
# Stop if running
|
||||||
|
if [ $(api_status) = 1 ]
|
||||||
|
then
|
||||||
|
kill -15 `cat "$PIDFILE"` && rm -f "$PIDFILE"
|
||||||
|
api_check_error $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
api_start
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
'start')
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
'stop')
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
'status')
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
'restart')
|
||||||
|
stop
|
||||||
|
sleep 5
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
'api_status')
|
||||||
|
api_status
|
||||||
|
;;
|
||||||
|
'api_start')
|
||||||
|
api_start
|
||||||
|
;;
|
||||||
|
'api_stop')
|
||||||
|
api_stop
|
||||||
|
;;
|
||||||
|
'api_restart')
|
||||||
|
api_restart
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $SCRIPTNAME {start|stop|status|restart}\n" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
14
src/main/java/org/minager/MinagerApplication.java
Normal file
14
src/main/java/org/minager/MinagerApplication.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package org.minager;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
public class MinagerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(MinagerApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/main/java/org/minager/account/AccountController.java
Normal file
96
src/main/java/org/minager/account/AccountController.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package org.minager.account;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.minager.core.entities.dto.PasswordWrapperDTO;
|
||||||
|
import org.minager.core.entities.dto.UserDTO;
|
||||||
|
import org.minager.core.entities.persistence.User;
|
||||||
|
import org.minager.core.security.TokenService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/account")
|
||||||
|
public class AccountController {
|
||||||
|
|
||||||
|
private static final String HEADER_TOKEN = "token";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AccountService accountService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TokenService tokenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log in the user in request body.
|
||||||
|
*
|
||||||
|
* @param pUser
|
||||||
|
* The user to connect.
|
||||||
|
* @param response
|
||||||
|
* The reponse injected by Spring.
|
||||||
|
* @return The connected user object.
|
||||||
|
* @throws IOException
|
||||||
|
* If credentials are bad.
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
public UserDTO login(@RequestBody UserDTO pUser, HttpServletResponse response) throws IOException {
|
||||||
|
return accountService.checkCredentials(response, pUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log out the user.
|
||||||
|
*
|
||||||
|
* @param pRequest
|
||||||
|
* The request injected by Spring.
|
||||||
|
*/
|
||||||
|
@GetMapping("/logout")
|
||||||
|
public void logout(HttpServletRequest pRequest) {
|
||||||
|
tokenService.removeUser(pRequest.getHeader(HEADER_TOKEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the user password.
|
||||||
|
*
|
||||||
|
* @param pPasswordWrapper
|
||||||
|
* The object which contains the old password for verification and
|
||||||
|
* the new password to set to the user.
|
||||||
|
* @param pRequest
|
||||||
|
* The request injected by Spring.
|
||||||
|
* @param pResponse
|
||||||
|
* The reponse injected by Spring.
|
||||||
|
* @throws IOException
|
||||||
|
* If the old password doesn't match to the user password in
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
@PutMapping("/changePassword")
|
||||||
|
public void changePassword(@RequestBody final PasswordWrapperDTO pPasswordWrapper,
|
||||||
|
final HttpServletRequest pRequest,
|
||||||
|
final HttpServletResponse pResponse) throws IOException {
|
||||||
|
final Optional<User> connectedUser = tokenService.getAuthenticatedUserByToken(pRequest);
|
||||||
|
if(connectedUser.isPresent()) {
|
||||||
|
accountService.changePassword(connectedUser.get(), pPasswordWrapper, pResponse);
|
||||||
|
} else {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/signin")
|
||||||
|
public UserDTO signin(@RequestBody final UserDTO pUser, final HttpServletResponse pResponse) throws IOException {
|
||||||
|
return accountService.signin(pUser, pResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/")
|
||||||
|
public void update(@RequestBody final UserDTO pUser, final HttpServletRequest pRequest,
|
||||||
|
final HttpServletResponse pResponse) throws IOException {
|
||||||
|
accountService.updateUser(pUser, pRequest, pResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/main/java/org/minager/account/AccountService.java
Normal file
124
src/main/java/org/minager/account/AccountService.java
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package org.minager.account;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.naming.AuthenticationException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.minager.core.entities.dto.PasswordWrapperDTO;
|
||||||
|
import org.minager.core.entities.dto.UserDTO;
|
||||||
|
import org.minager.core.entities.persistence.User;
|
||||||
|
import org.minager.core.repositories.UserRepository;
|
||||||
|
import org.minager.core.security.TokenService;
|
||||||
|
import org.minager.core.utils.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AccountService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TokenService tokenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the user credentials and generate him a token if they are correct.
|
||||||
|
*
|
||||||
|
* @param pUser
|
||||||
|
* The user sent from client.
|
||||||
|
* @return The user populated with the generated token.
|
||||||
|
* @throws IOException
|
||||||
|
* If the credentials are bad.
|
||||||
|
* @throws AuthenticationException
|
||||||
|
* If the credentials are wrong.
|
||||||
|
*/
|
||||||
|
public UserDTO checkCredentials(HttpServletResponse pResponse, UserDTO pUser) throws IOException {
|
||||||
|
UserDTO result = null;
|
||||||
|
|
||||||
|
Optional<User> user = userRepository.findByEmail(pUser.getEmail());
|
||||||
|
|
||||||
|
if(user.isPresent() && StringUtils.compareHash(pUser.getPassword(), user.get().getPassword())) {
|
||||||
|
tokenService.addUser(user.get());
|
||||||
|
result = new UserDTO(user.get(), true);
|
||||||
|
} else {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePassword(final User pUser, final PasswordWrapperDTO pPasswordWrapper,
|
||||||
|
final HttpServletResponse pResponse) throws IOException {
|
||||||
|
if(pPasswordWrapper.getNewPassword().equals(pPasswordWrapper.getConfirmPassword())) {
|
||||||
|
// We fetch the connected user from database to get his hashed password
|
||||||
|
final Optional<User> userFromDb = userRepository.findById(pUser.getId());
|
||||||
|
if(userFromDb.isPresent() && StringUtils.compareHash(pPasswordWrapper.getOldPassword(),
|
||||||
|
userFromDb.get().getPassword())) {
|
||||||
|
userFromDb.get().setPassword(StringUtils.hashPassword(pPasswordWrapper.getNewPassword()));
|
||||||
|
userRepository.save(userFromDb.get());
|
||||||
|
} else {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
|
||||||
|
"Le mot de passe saisi ne correspond pas au votre.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_BAD_REQUEST,
|
||||||
|
"Le mot de passe saisi ne correspond pas au votre.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDTO signin(final UserDTO pUser, final HttpServletResponse pResponse) throws IOException {
|
||||||
|
User user = new User();
|
||||||
|
|
||||||
|
if(pUser.getName() == null || pUser.getEmail() == null || pUser.getPassword() == null || "".equals(pUser.getPassword().trim())) {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||||
|
} else if(userRepository.findByEmail(pUser.getEmail()).isPresent()) {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_CONFLICT);
|
||||||
|
} else {
|
||||||
|
user.setName(pUser.getName());
|
||||||
|
user.setEmail(pUser.getEmail());
|
||||||
|
user.setPassword(StringUtils.hashPassword(pUser.getPassword()));
|
||||||
|
user.setInscriptionDate(new Date());
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UserDTO(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateUser(final UserDTO pUser, final HttpServletRequest pRequest,
|
||||||
|
final HttpServletResponse pResponse) throws IOException {
|
||||||
|
final Optional<User> connectedUserOpt = tokenService.getAuthenticatedUserByToken(pRequest);
|
||||||
|
if(connectedUserOpt.isPresent()) {
|
||||||
|
final User connectedUser = connectedUserOpt.get();
|
||||||
|
|
||||||
|
final Optional<User> userFromDb = userRepository.findByEmail(pUser.getEmail());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If a user is returned by the repository, that's the email adress is used, but
|
||||||
|
* if it is not the same as the connected user, that's the email adress
|
||||||
|
* corresponds to another user. So a 409 error is sent. Otherwise, if no user is
|
||||||
|
* returned by the repository, that's the email adress is free to be used. So,
|
||||||
|
* user can change him email adress.
|
||||||
|
*/
|
||||||
|
if(userFromDb.isPresent() && !connectedUser.getEmail().equals(userFromDb.get().getEmail())) {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_CONFLICT);
|
||||||
|
} else {
|
||||||
|
connectedUser.setName(pUser.getName());
|
||||||
|
connectedUser.setEmail(pUser.getEmail());
|
||||||
|
|
||||||
|
userRepository.save(connectedUser);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/main/java/org/minager/core/AbstractFilter.java
Normal file
129
src/main/java/org/minager/core/AbstractFilter.java
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package org.minager.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.minager.core.security.Route;
|
||||||
|
import org.minager.core.utils.StringUtils;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all filters of the application.<br/>
|
||||||
|
* <br/>
|
||||||
|
* The children classes have to implements the method
|
||||||
|
* {@link AbstractFilter#getClass()} to set the URLs filtered (with all or some
|
||||||
|
* http methods), and the method
|
||||||
|
* {@link AbstractFilter#filter(HttpServletRequest, ServletResponse, FilterChain)}
|
||||||
|
* to define the filter processing.
|
||||||
|
*
|
||||||
|
* @author Takiguchi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class AbstractFilter implements Filter {
|
||||||
|
|
||||||
|
/** Regex url path prefix for method {@link this#isRequestFiltered(String)}. */
|
||||||
|
private static final String PREFIX_URL_PATH = "https?:\\/\\/.*(:\\d{0,5})?";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of routes which will be processed by the filter.
|
||||||
|
*
|
||||||
|
* @return The routes.
|
||||||
|
*/
|
||||||
|
protected abstract List<Route> getRoutes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter actions for its processing.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The http request.
|
||||||
|
* @param response
|
||||||
|
* The response.
|
||||||
|
* @param chain
|
||||||
|
* The chain.
|
||||||
|
*/
|
||||||
|
protected abstract void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
|
||||||
|
if(isRequestFiltered(httpRequest.getRequestURL().toString(), httpRequest.getMethod())) {
|
||||||
|
filter(httpRequest, (HttpServletResponse) response, chain);
|
||||||
|
} else {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the url is allowed with the given method in parameters.
|
||||||
|
*
|
||||||
|
* @param pRequestUrl
|
||||||
|
* The url request.
|
||||||
|
* @param pRequestMethod
|
||||||
|
* The http method of the request.
|
||||||
|
* @return {@code true} if the url is allowed with the method, {@code false}
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
boolean isRequestFiltered(final String pRequestUrl, final String pRequestMethod) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
for(final Route route : getRoutes()) {
|
||||||
|
/*
|
||||||
|
* Check urls matching, and if the method of the route isn't set, all methods
|
||||||
|
* are allowed. Otherwise, we check the methods too.
|
||||||
|
*/
|
||||||
|
if(Pattern.matches(StringUtils.concat(PREFIX_URL_PATH, route.getUrl()), pRequestUrl)) {
|
||||||
|
if(!route.getMethod().isPresent() || isMethodFiltered(route, pRequestMethod)) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the route do filter the method in parameters.
|
||||||
|
*
|
||||||
|
* @param pRoute
|
||||||
|
* The registered route.
|
||||||
|
* @param pRequestMethod
|
||||||
|
* The http method to check with the registered route.
|
||||||
|
*/
|
||||||
|
boolean isMethodFiltered(final Route pRoute, final String pRequestMethod) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
if(pRoute.getMethod().isPresent()) {
|
||||||
|
for(final HttpMethod routeMethod : pRoute.getMethod().get()) {
|
||||||
|
if(routeMethod.name().equals(pRequestMethod)) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/java/org/minager/core/config/JpaConfiguration.java
Normal file
42
src/main/java/org/minager/core/config/JpaConfiguration.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package org.minager.core.config;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||||
|
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EntityScan("org.minager")
|
||||||
|
@EnableTransactionManagement
|
||||||
|
@EnableJpaRepositories("org.minager")
|
||||||
|
@PropertySource("classpath:application.properties")
|
||||||
|
public class JpaConfiguration {
|
||||||
|
|
||||||
|
@Value("${spring.datasource.driverClassName}")
|
||||||
|
private String driverClassName;
|
||||||
|
|
||||||
|
@Value("${spring.datasource.url}")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@Value("${spring.datasource.username}")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Value("${spring.datasource.password}")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Bean(name="dataSource")
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return DataSourceBuilder.create()
|
||||||
|
.username(username)
|
||||||
|
.password(password)
|
||||||
|
.url(url)
|
||||||
|
.driverClassName(driverClassName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/java/org/minager/core/constant/ResultCode.java
Normal file
18
src/main/java/org/minager/core/constant/ResultCode.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.minager.core.constant;
|
||||||
|
|
||||||
|
public enum ResultCode {
|
||||||
|
SUCCESS(0),
|
||||||
|
FAILED(1),
|
||||||
|
STATE_UNCHANGED(2),
|
||||||
|
ILLEGAL_ARGUMENT(3);
|
||||||
|
|
||||||
|
private int val;
|
||||||
|
|
||||||
|
private ResultCode(final int pVal) {
|
||||||
|
val = pVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int val() {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.minager.core.entities.business;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object that contains the results of a system command.
|
||||||
|
*
|
||||||
|
* @author takiguchi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SystemResult {
|
||||||
|
/** The result code of the command. */
|
||||||
|
private int resultCode;
|
||||||
|
/** The standard output of the command. */
|
||||||
|
private StringBuilder stdOut;
|
||||||
|
/** The standard errors output of the command. */
|
||||||
|
private StringBuilder stdErr;
|
||||||
|
|
||||||
|
public SystemResult() {
|
||||||
|
super();
|
||||||
|
stdOut = new StringBuilder();
|
||||||
|
stdErr = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the result code.
|
||||||
|
*
|
||||||
|
* @return the result code
|
||||||
|
*/
|
||||||
|
public int getResultCode() {
|
||||||
|
return resultCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the result code.
|
||||||
|
*
|
||||||
|
* @param resultCode
|
||||||
|
* the new result code
|
||||||
|
*/
|
||||||
|
public void setResultCode(int resultCode) {
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the std out.
|
||||||
|
*
|
||||||
|
* @return the std out
|
||||||
|
*/
|
||||||
|
public String getStdOut() {
|
||||||
|
return stdOut.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an output line to the {@code stdOut}.
|
||||||
|
*
|
||||||
|
* @param pLine
|
||||||
|
* The output line to append.
|
||||||
|
*/
|
||||||
|
public void addOutputLine(final String pLine) {
|
||||||
|
stdOut.append(pLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the std out.
|
||||||
|
*
|
||||||
|
* @param stdOut
|
||||||
|
* the new std out
|
||||||
|
*/
|
||||||
|
public void setStdOut(String stdOut) {
|
||||||
|
this.stdOut = new StringBuilder(stdOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the std err.
|
||||||
|
*
|
||||||
|
* @return the std err
|
||||||
|
*/
|
||||||
|
public String getStdErr() {
|
||||||
|
return stdErr.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an error line to the {@code stdErr}.
|
||||||
|
*
|
||||||
|
* @param pLine
|
||||||
|
* The error line to append.
|
||||||
|
*/
|
||||||
|
public void addErrorLine(final String pLine) {
|
||||||
|
stdErr.append(pLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the std err.
|
||||||
|
*
|
||||||
|
* @param stdErr
|
||||||
|
* the new std err
|
||||||
|
*/
|
||||||
|
public void setStdErr(String stdErr) {
|
||||||
|
this.stdErr = new StringBuilder(stdErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.minager.core.entities.dto;
|
||||||
|
|
||||||
|
public class PasswordWrapperDTO {
|
||||||
|
|
||||||
|
private String oldPassword;
|
||||||
|
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
private String confirmPassword;
|
||||||
|
|
||||||
|
public String getOldPassword() {
|
||||||
|
return oldPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOldPassword(String oldPassword) {
|
||||||
|
this.oldPassword = oldPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewPassword() {
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewPassword(String newPassword) {
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfirmPassword() {
|
||||||
|
return confirmPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfirmPassword(String confirmPassword) {
|
||||||
|
this.confirmPassword = confirmPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/main/java/org/minager/core/entities/dto/UserDTO.java
Normal file
97
src/main/java/org/minager/core/entities/dto/UserDTO.java
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package org.minager.core.entities.dto;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.minager.core.entities.persistence.User;
|
||||||
|
|
||||||
|
public class UserDTO {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
private Date inscriptionDate;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
public UserDTO() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDTO(final User pUser) {
|
||||||
|
key = pUser.getKey();
|
||||||
|
name = pUser.getName();
|
||||||
|
email = pUser.getEmail();
|
||||||
|
image = pUser.getImage();
|
||||||
|
inscriptionDate = pUser.getInscriptionDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDTO(final User pUser, final boolean pWithToken) {
|
||||||
|
this(pUser);
|
||||||
|
if(pWithToken) {
|
||||||
|
token = pUser.getToken().getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImage() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImage(String image) {
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getInscriptionDate() {
|
||||||
|
return inscriptionDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInscriptionDate(Date inscriptionDate) {
|
||||||
|
this.inscriptionDate = inscriptionDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/main/java/org/minager/core/entities/persistence/User.java
Normal file
126
src/main/java/org/minager/core/entities/persistence/User.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package org.minager.core.entities.persistence;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.SequenceGenerator;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
import javax.persistence.TemporalType;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Generated;
|
||||||
|
import org.hibernate.annotations.GenerationTime;
|
||||||
|
import org.minager.core.entities.security.Token;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name="`user`")
|
||||||
|
public class User implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/* ******************* */
|
||||||
|
/* Attributes */
|
||||||
|
/* ******************* */
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="user_id_seq")
|
||||||
|
@SequenceGenerator(name="user_id_seq", sequenceName="user_id_seq", allocationSize=1)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
// This annotation serves to fetch the attribute after an insert into db
|
||||||
|
@Generated(GenerationTime.ALWAYS)
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@Column(name = "inscription_date")
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
private Date inscriptionDate;
|
||||||
|
|
||||||
|
/** Authentication token. */
|
||||||
|
private transient Token token;
|
||||||
|
|
||||||
|
/* ******************* */
|
||||||
|
/* Constructors */
|
||||||
|
/* ******************* */
|
||||||
|
public User() {
|
||||||
|
super();
|
||||||
|
token = new Token();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ******************* */
|
||||||
|
/* Getters & Setters */
|
||||||
|
/* ******************* */
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
if(this.id != null) {
|
||||||
|
throw new IllegalAccessError("It's not allowed to rewrite the id entity.");
|
||||||
|
}
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImage() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImage(String image) {
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getInscriptionDate() {
|
||||||
|
return inscriptionDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInscriptionDate(Date inscriptionDate) {
|
||||||
|
this.inscriptionDate = inscriptionDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Token getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
src/main/java/org/minager/core/entities/security/Token.java
Normal file
82
src/main/java/org/minager/core/entities/security/Token.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package org.minager.core.entities.security;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class Token {
|
||||||
|
/** The metric in which the validation delay is defined. */
|
||||||
|
private static final int METRIC = Calendar.MINUTE;
|
||||||
|
/** Number of {@link METRIC} after that the token become invalid. */
|
||||||
|
private static final int DELAY = 30;
|
||||||
|
|
||||||
|
/** The Constant BITS_NUMBER. */
|
||||||
|
private static final int BITS_NUMBER = 1000;
|
||||||
|
/** The Constant RADIX. */
|
||||||
|
private static final int RADIX = 32;
|
||||||
|
|
||||||
|
/** The value. */
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last access date. For each request to the server, this date is consulted
|
||||||
|
* and if the valid delay is ok, this date must be updated.
|
||||||
|
*/
|
||||||
|
private Calendar lastAccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new token.
|
||||||
|
*/
|
||||||
|
public Token() {
|
||||||
|
super();
|
||||||
|
value = new BigInteger(BITS_NUMBER, new SecureRandom()).toString(RADIX);
|
||||||
|
lastAccess = Calendar.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value.
|
||||||
|
*
|
||||||
|
* @return the value
|
||||||
|
*/
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last access date.
|
||||||
|
*
|
||||||
|
* @return the last access date
|
||||||
|
*/
|
||||||
|
public Calendar getLastAccess() {
|
||||||
|
return lastAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last access date.
|
||||||
|
*/
|
||||||
|
public void setLastAccess() {
|
||||||
|
lastAccess = Calendar.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate if the token is still valid.<br/>
|
||||||
|
* A token is valid is its {@link Token#lastAccess} is after the current
|
||||||
|
* date minus the {@link Token#DELAY} {@link Token#METRIC}.<br/>
|
||||||
|
* <br/>
|
||||||
|
* Example:<br/>
|
||||||
|
* {@link Token#DELAY} = 30 and {@link Token#METRIC} =
|
||||||
|
* {@link Calendar#MINUTE}.<br/>
|
||||||
|
* A token is valid only on the 30 minutes after its
|
||||||
|
* {@link Token#lastAccess}.<br/>
|
||||||
|
* If the current date-time minus the 30 minutes is before the
|
||||||
|
* {@link Token#lastAccess}, the token is still valid.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the token is still valid, {@code false}
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
final Calendar lastTimeValidation = Calendar.getInstance();
|
||||||
|
lastTimeValidation.add(METRIC, -DELAY);
|
||||||
|
return lastAccess.getTime().after(lastTimeValidation.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.minager.core.repositories;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
|
||||||
|
import org.minager.core.entities.persistence.User;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends CrudRepository<User, Long> {
|
||||||
|
|
||||||
|
Optional<User> findByEmail(@Param("email") final String pEmail);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the password in parameters is the passwords of the user in
|
||||||
|
* database.
|
||||||
|
*
|
||||||
|
* @param pId
|
||||||
|
* The user id.
|
||||||
|
* @param pPassword
|
||||||
|
* The password to check.
|
||||||
|
* @return {@code true} if the password is the user password in database,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
@Query(value = "SELECT CASE WHEN EXISTS(" +
|
||||||
|
" SELECT id FROM \"user\" WHERE id = :id AND password = :password" +
|
||||||
|
") THEN TRUE ELSE FALSE END", nativeQuery = true)
|
||||||
|
boolean checkPassword(@Param("id") final Long pId, @Param("password") final String pPassword);
|
||||||
|
|
||||||
|
@Query(value = "UPDATE \"user\" SET password = :password WHERE id = :id", nativeQuery = true)
|
||||||
|
@Transactional
|
||||||
|
@Modifying
|
||||||
|
void updatePassword(@Param("id") final Long pId, @Param("password") final String pPassword);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.minager.core.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.minager.core.AbstractFilter;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class AuthenticationFilter extends AbstractFilter {
|
||||||
|
|
||||||
|
private static final String HTTP_OPTIONS = "OPTIONS";
|
||||||
|
|
||||||
|
private static final String HEADER_TOKEN = "token";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TokenService tokenService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Route> getRoutes() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new Route("\\/api\\/server\\/start"),
|
||||||
|
new Route("\\/api\\/server\\/stop"),
|
||||||
|
new Route("\\/api\\/server\\/restart")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
if(HTTP_OPTIONS.equals(request.getMethod()) || tokenService.isUserConnected(request.getHeader(HEADER_TOKEN))) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
44
src/main/java/org/minager/core/security/CorsFilter.java
Normal file
44
src/main/java/org/minager/core/security/CorsFilter.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package org.minager.core.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class CorsFilter implements Filter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
httpResponse.addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
httpResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, HEAD");
|
||||||
|
httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
|
||||||
|
httpResponse.addHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Credentials");
|
||||||
|
httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
httpResponse.addIntHeader("Access-Control-Max-Age", 10);
|
||||||
|
chain.doFilter(request, httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
src/main/java/org/minager/core/security/Route.java
Normal file
71
src/main/java/org/minager/core/security/Route.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package org.minager.core.security;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route for filter matching.
|
||||||
|
*
|
||||||
|
* @author Takiguchi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Route {
|
||||||
|
/** The regex to match urls. */
|
||||||
|
private String url;
|
||||||
|
/** The http method to match. Use a {@link Optional#empty()} to match all methods. */
|
||||||
|
private Optional<List<HttpMethod>> method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instanciate a vierge route.
|
||||||
|
*/
|
||||||
|
public Route() {
|
||||||
|
super();
|
||||||
|
url = "";
|
||||||
|
method = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instanciate a route for all http methods.
|
||||||
|
*
|
||||||
|
* @param pUrl
|
||||||
|
* The regex to match urls.
|
||||||
|
*/
|
||||||
|
public Route(final String pUrl) {
|
||||||
|
this();
|
||||||
|
this.url = pUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instanciate a route for methods given in parameters
|
||||||
|
*
|
||||||
|
* @param pUrl
|
||||||
|
* The regex to match urls.
|
||||||
|
* @param pMethod
|
||||||
|
* The http method to match. Use a {@link Optional#empty()} to match
|
||||||
|
* all methods.
|
||||||
|
*/
|
||||||
|
public Route(final String pUrl, final HttpMethod... pMethods) {
|
||||||
|
this(pUrl);
|
||||||
|
this.method = Optional.of(Arrays.asList(pMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<List<HttpMethod>> getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethod(HttpMethod pMethods) {
|
||||||
|
this.method = Optional.of(Arrays.asList(pMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
132
src/main/java/org/minager/core/security/TokenService.java
Normal file
132
src/main/java/org/minager/core/security/TokenService.java
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package org.minager.core.security;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.minager.core.entities.persistence.User;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TokenService {
|
||||||
|
/** Map of connected users. */
|
||||||
|
private static final Map<String, User> connectedUsers;
|
||||||
|
|
||||||
|
private static final String HEADER_TOKEN = "token";
|
||||||
|
|
||||||
|
private static final long INTERVAL_USER_CLEANING = 5;
|
||||||
|
|
||||||
|
private static final long INTERVAL_USER_CLEANING_VAL = INTERVAL_USER_CLEANING * 60 * 1000;
|
||||||
|
|
||||||
|
private Date lastUsersCleaning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
connectedUsers = new TreeMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the token matches to a user session, and if it is still valid.
|
||||||
|
*
|
||||||
|
* @param pToken
|
||||||
|
* The token to check.
|
||||||
|
* @return {@code true} if the token is still valid, {@code false}
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isUserConnected(final String pToken) {
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
if (pToken != null && connectedUsers.containsKey(pToken)) {
|
||||||
|
if (connectedUsers.get(pToken).getToken().isValid()) {
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
connectedUsers.remove(pToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear all the expired sessions
|
||||||
|
final Date now = new Date();
|
||||||
|
if(lastUsersCleaning == null || now.getTime() - lastUsersCleaning.getTime() >= INTERVAL_USER_CLEANING_VAL) {
|
||||||
|
new Thread(this::clearExpiredUsers).start();
|
||||||
|
lastUsersCleaning = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove from the connected users map all the elements which their token is
|
||||||
|
* expired.
|
||||||
|
*/
|
||||||
|
private void clearExpiredUsers() {
|
||||||
|
synchronized (this) {
|
||||||
|
List<User> usersToRemove = new LinkedList<>();
|
||||||
|
connectedUsers.entrySet().stream().forEach(user -> {
|
||||||
|
if(!user.getValue().getToken().isValid()) {
|
||||||
|
usersToRemove.add(user.getValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
usersToRemove.stream().map(User::getKey).forEach(connectedUsers::remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the user to the connected users map.
|
||||||
|
*
|
||||||
|
* @param pUser
|
||||||
|
* The user to add.
|
||||||
|
*/
|
||||||
|
public void addUser(final User pUser) {
|
||||||
|
if(connectedUsers.get(pUser.getToken().getValue()) == null) {
|
||||||
|
connectedUsers.put(pUser.getToken().getValue(), pUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the user token last access date in the token service.
|
||||||
|
*
|
||||||
|
* @param pToken
|
||||||
|
* The user token.
|
||||||
|
*/
|
||||||
|
public void refreshUserToken(final String pToken) {
|
||||||
|
final User user = connectedUsers.get(pToken);
|
||||||
|
if(user != null) {
|
||||||
|
user.getToken().setLastAccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the user to the connected users map.
|
||||||
|
*
|
||||||
|
* @param pUser
|
||||||
|
* The user to remove.
|
||||||
|
*/
|
||||||
|
public void removeUser(final User pUser) {
|
||||||
|
removeUser(pUser.getToken().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the user associated to the token given in parameters, from the
|
||||||
|
* connected users map.
|
||||||
|
*
|
||||||
|
* @param pToken
|
||||||
|
* The user to delete token.
|
||||||
|
*/
|
||||||
|
public void removeUser(final String pToken) {
|
||||||
|
if(pToken != null && connectedUsers.containsKey(pToken)) {
|
||||||
|
connectedUsers.remove(pToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> getAuthenticatedUserByToken(final HttpServletRequest pRequest) {
|
||||||
|
return Optional.ofNullable(connectedUsers.get(pRequest.getHeader(HEADER_TOKEN)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.minager.core.services.business;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import org.minager.core.entities.business.SystemResult;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SystemService {
|
||||||
|
|
||||||
|
public SystemResult executeCommand(final String pCommand, final String... pArgs) {
|
||||||
|
final String command = buildCommand(pCommand, pArgs);
|
||||||
|
|
||||||
|
final SystemResult commandResults = new SystemResult();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Process creation and execution of the command.
|
||||||
|
final Process process = Runtime.getRuntime().exec(command);
|
||||||
|
|
||||||
|
// Waiting the end of the command execution.
|
||||||
|
process.waitFor();
|
||||||
|
|
||||||
|
// Getting of the stantard command output, and the standard error output.
|
||||||
|
BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
|
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||||
|
|
||||||
|
String tempLine = null;
|
||||||
|
while((tempLine = outputReader.readLine()) != null) {
|
||||||
|
commandResults.addOutputLine(tempLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
while((tempLine = errorReader.readLine()) != null) {
|
||||||
|
commandResults.addErrorLine(tempLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
commandResults.setResultCode(process.exitValue());
|
||||||
|
} catch(IOException | InterruptedException ex) {
|
||||||
|
// LOGGER.error(StringUtils.concat("Une erreur est survenue lors de l'exécution de la commande \"", command,
|
||||||
|
// "\"."), ex);
|
||||||
|
System.err.println(ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the command in form of one string from the parameters.
|
||||||
|
*
|
||||||
|
* @param pCommand
|
||||||
|
* The command to execute.
|
||||||
|
* @param pArgs
|
||||||
|
* The command arguments, could be {@code null}.
|
||||||
|
* @return The command built.
|
||||||
|
*/
|
||||||
|
private String buildCommand(final String pCommand, final Object... pArgs) {
|
||||||
|
final StringBuilder command = new StringBuilder(pCommand);
|
||||||
|
|
||||||
|
if (pArgs != null) {
|
||||||
|
for (final Object arg : pArgs) {
|
||||||
|
command.append(" ").append(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return command.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/main/java/org/minager/core/utils/RegexUtils.java
Normal file
82
src/main/java/org/minager/core/utils/RegexUtils.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package org.minager.core.utils;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class RegexUtils {
|
||||||
|
|
||||||
|
private static final String EMAIL_REGEX = "^.*@.*\\..{2,}$";
|
||||||
|
private static final String LOWER_LETTERS_REGEX = ".*[a-z].*";
|
||||||
|
private static final String UPPER_LETTERS_REGEX = ".*[A-Z].*";
|
||||||
|
private static final String NUMBER_REGEX = ".*[0-9].*";
|
||||||
|
private static final String SPECIAL_CHAR_REGEX = ".*\\W.*";
|
||||||
|
private static final String NUMBER_ONLY_REGEX = "^[0-9]+$";
|
||||||
|
|
||||||
|
// La portée "package" permet à la classe StringUtils d'utiliser les patterns
|
||||||
|
// suivants :
|
||||||
|
static final Pattern EMAIL_PATTERN;
|
||||||
|
static final Pattern LOWER_LETTERS_PATTERN;
|
||||||
|
static final Pattern UPPER_LETTERS_PATTERN;
|
||||||
|
static final Pattern NUMBER_PATTERN;
|
||||||
|
static final Pattern SPECIAL_CHAR_PATTERN;
|
||||||
|
static final Pattern NUMBER_ONLY_PATTERN;
|
||||||
|
|
||||||
|
static {
|
||||||
|
EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
|
||||||
|
LOWER_LETTERS_PATTERN = Pattern.compile(LOWER_LETTERS_REGEX);
|
||||||
|
UPPER_LETTERS_PATTERN = Pattern.compile(UPPER_LETTERS_REGEX);
|
||||||
|
NUMBER_PATTERN = Pattern.compile(NUMBER_REGEX);
|
||||||
|
SPECIAL_CHAR_PATTERN = Pattern.compile(SPECIAL_CHAR_REGEX);
|
||||||
|
NUMBER_ONLY_PATTERN = Pattern.compile(NUMBER_ONLY_REGEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chekcs if {@code pString} corresponds to an email address.
|
||||||
|
*
|
||||||
|
* @param pString
|
||||||
|
* The string which should be an email address.
|
||||||
|
* @return {@code true} if {@link pString} corresponds to an email address,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isEmail(final String pString) {
|
||||||
|
return EMAIL_PATTERN.matcher(pString).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the sequences of {@code pString} matched by the {@code pRegex}
|
||||||
|
* with the {@code pReplacingString}.
|
||||||
|
*
|
||||||
|
* @param pString
|
||||||
|
* The string to update.
|
||||||
|
* @param pRegex
|
||||||
|
* The regex to match the sentences to replace.
|
||||||
|
* @param pReplacingString
|
||||||
|
* The string to replace the sentences which match with the
|
||||||
|
* regex.
|
||||||
|
* @return The new string.
|
||||||
|
*/
|
||||||
|
public static String replaceSequence(final String pString,
|
||||||
|
final String pRegex, final String pReplacingString) {
|
||||||
|
return Pattern.compile(pRegex).matcher(pString)
|
||||||
|
.replaceAll(pReplacingString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if {@code pString} corresponds to a number.
|
||||||
|
*
|
||||||
|
* @param pString
|
||||||
|
* The string which should be a number.
|
||||||
|
* @return {@code true} if {@code pString} corresponds to a number,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isNumber(final String pString) {
|
||||||
|
return NUMBER_ONLY_PATTERN.matcher(pString).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getGroup(final String regex, final int numeroGroupe, final String chaine) {
|
||||||
|
final Pattern pattern = Pattern.compile(regex);
|
||||||
|
final Matcher matcher = pattern.matcher(chaine);
|
||||||
|
matcher.find();
|
||||||
|
return matcher.group(numeroGroupe);
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/main/java/org/minager/core/utils/StringUtils.java
Normal file
93
src/main/java/org/minager/core/utils/StringUtils.java
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package org.minager.core.utils;
|
||||||
|
|
||||||
|
import org.mindrot.jbcrypt.BCrypt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic methods about {@link String} class.
|
||||||
|
*
|
||||||
|
* @author takiguchi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class StringUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate if {@code pString} is null or just composed of spaces.
|
||||||
|
*
|
||||||
|
* @param pString
|
||||||
|
* The string to test.
|
||||||
|
* @return {@code true} if {@code pString} is null or just composed of
|
||||||
|
* spaces, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isNull(final String chaine) {
|
||||||
|
return chaine == null || chaine.trim().length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the password given into parameters.
|
||||||
|
*
|
||||||
|
* @param pPassword The password to hash.
|
||||||
|
* @return The password hashed.
|
||||||
|
*/
|
||||||
|
public static String hashPassword(final String pPassword) {
|
||||||
|
return hashString(pPassword, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hashString(final String pString, final int pSalt) {
|
||||||
|
return BCrypt.hashpw(pString, BCrypt.gensalt(pSalt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the password and the hashed string given into parameters.
|
||||||
|
*
|
||||||
|
* @param pPassword
|
||||||
|
* The password to compare to the hashed string.
|
||||||
|
* @param pHashToCompare
|
||||||
|
* The hashed string to compare to the password.
|
||||||
|
* @return {@code true} if the password matches to the hashed string.
|
||||||
|
*/
|
||||||
|
public static boolean compareHash(final String pPassword, final String pHashToCompare) {
|
||||||
|
return BCrypt.checkpw(pPassword, pHashToCompare);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenate the parameters to form just one single string.
|
||||||
|
*
|
||||||
|
* @param pArgs
|
||||||
|
* The strings to concatenate.
|
||||||
|
* @return The parameters concatenated.
|
||||||
|
*/
|
||||||
|
public static String concat(final Object... pArgs) {
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
for (final Object arg : pArgs) {
|
||||||
|
result.append(arg);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String printStrings(final String... pStrings) {
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
for (int i = 0 ; i < pStrings.length ; i++) {
|
||||||
|
result.append(pStrings[i]);
|
||||||
|
if(i < pStrings.length - 1) {
|
||||||
|
result.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containLowercase(final String pString) {
|
||||||
|
return RegexUtils.LOWER_LETTERS_PATTERN.matcher(pString).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containUppercase(final String pString) {
|
||||||
|
return RegexUtils.UPPER_LETTERS_PATTERN.matcher(pString).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containNumber(final String pString) {
|
||||||
|
return RegexUtils.NUMBER_PATTERN.matcher(pString).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containSpecialChar(final String pString) {
|
||||||
|
return RegexUtils.SPECIAL_CHAR_PATTERN.matcher(pString).find();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.minager.serverhandling;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/server")
|
||||||
|
public class ServerHandlingController {
|
||||||
|
@Autowired
|
||||||
|
private ServerHandlingService serverHandlingService;
|
||||||
|
|
||||||
|
@GetMapping("/status")
|
||||||
|
public int getStatus() {
|
||||||
|
return serverHandlingService.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/start")
|
||||||
|
public void startServer(final HttpServletResponse pResponse) throws IOException {
|
||||||
|
serverHandlingService.startServer(pResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/stop")
|
||||||
|
public void stopServer(final HttpServletResponse pResponse) throws IOException {
|
||||||
|
serverHandlingService.stopServer(pResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/restart")
|
||||||
|
public void restartServer(final HttpServletResponse pResponse) throws IOException {
|
||||||
|
serverHandlingService.restartServer(pResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.minager.serverhandling;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.minager.core.constant.ResultCode;
|
||||||
|
import org.minager.core.entities.business.SystemResult;
|
||||||
|
import org.minager.core.services.business.SystemService;
|
||||||
|
import org.minager.core.utils.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ServerHandlingService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemService systemService;
|
||||||
|
|
||||||
|
@Value("${minecraft.server.path}")
|
||||||
|
private String minecraftServerPath;
|
||||||
|
|
||||||
|
@Value("${minecraft.server.shell.name}")
|
||||||
|
private String minecraftServerShellName;
|
||||||
|
|
||||||
|
private String buildMinecraftServerShellPath() {
|
||||||
|
return StringUtils.concat(minecraftServerPath,
|
||||||
|
minecraftServerPath.charAt(minecraftServerPath.length() - 1) == '/' ? "" : '/',
|
||||||
|
minecraftServerShellName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
final SystemResult shellResult = systemService.executeCommand(buildMinecraftServerShellPath(), "api_status");
|
||||||
|
return Integer.parseInt(shellResult.getStdOut());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startOrStopServer(final HttpServletResponse pResponse, final String pAction) throws IOException {
|
||||||
|
final SystemResult shellResult = systemService.executeCommand(buildMinecraftServerShellPath(), pAction);
|
||||||
|
|
||||||
|
if(shellResult.getResultCode() == ResultCode.FAILED.val()) {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||||
|
} else if(shellResult.getResultCode() == ResultCode.STATE_UNCHANGED.val()) {
|
||||||
|
pResponse.sendError(HttpServletResponse.SC_CONFLICT);
|
||||||
|
} // else -> SUCCESS, so code 200
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServer(final HttpServletResponse pResponse) throws IOException {
|
||||||
|
startOrStopServer(pResponse, "api_start");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopServer(final HttpServletResponse pResponse) throws IOException {
|
||||||
|
startOrStopServer(pResponse, "api_stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartServer(final HttpServletResponse pResponse) throws IOException {
|
||||||
|
startOrStopServer(pResponse, "api_restart");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/main/resources/application.properties
Normal file
15
src/main/resources/application.properties
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# ***********************************************
|
||||||
|
# Hibernate database configuration
|
||||||
|
# ***********************************************
|
||||||
|
spring.datasource.driverClassName=org.postgresql.Driver
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/db_minager
|
||||||
|
spring.datasource.username=minager
|
||||||
|
spring.datasource.password=P@ssword
|
||||||
|
# Disable feature detection by this undocumented parameter. Check the org.hibernate.engine.jdbc.internal.JdbcServiceImpl.configure method for more details.
|
||||||
|
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false
|
||||||
|
|
||||||
|
# ***********************************************
|
||||||
|
# Application custom parameters
|
||||||
|
# ***********************************************
|
||||||
|
minecraft.server.path=/home/minecraft/server
|
||||||
|
minecraft.server.shell.name=minecraft-server.sh
|
||||||
25
src/main/sql/0_init_db.sql
Normal file
25
src/main/sql/0_init_db.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-- Execute with psql as postgres user
|
||||||
|
create user minager_admin with password 'P@ssword';
|
||||||
|
create user minager with password 'P@ssword';
|
||||||
|
create database db_minager owner minager_admin;
|
||||||
|
\c db_minager;
|
||||||
|
|
||||||
|
-- ******************************************************************
|
||||||
|
-- T A B L E S
|
||||||
|
-- ******************************************************************
|
||||||
|
drop table if exists "user";
|
||||||
|
|
||||||
|
create table "user" (
|
||||||
|
"id" serial,
|
||||||
|
"key" varchar,
|
||||||
|
"name" varchar,
|
||||||
|
"email" varchar,
|
||||||
|
"password" varchar,
|
||||||
|
"image" varchar,
|
||||||
|
"inscription_date" timestamp,
|
||||||
|
constraint user_pk primary key ("id"),
|
||||||
|
constraint user_key_unique unique ("key"),
|
||||||
|
constraint user_email_unique unique ("email")
|
||||||
|
);
|
||||||
|
grant select, insert, update, delete on "user" to minager;
|
||||||
|
grant usage, select, update on user_id_seq to minager;
|
||||||
13
src/main/ts/.editorconfig
Normal file
13
src/main/ts/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Editor configuration, see http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
27
src/main/ts/README.md
Normal file
27
src/main/ts/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Ts
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.2.3.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||||
137
src/main/ts/angular.json
Normal file
137
src/main/ts/angular.json
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"ts": {
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"projectType": "application",
|
||||||
|
"prefix": "app",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"styleext": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/ts",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "src/tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.png",
|
||||||
|
"src/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"node_modules/font-awesome/scss/font-awesome.scss",
|
||||||
|
"node_modules/angular-bootstrap-md/scss/bootstrap/bootstrap.scss",
|
||||||
|
"node_modules/angular-bootstrap-md/scss/mdb-free.scss",
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
"node_modules/chart.js/dist/Chart.js",
|
||||||
|
"node_modules/hammerjs/hammer.min.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"extractCss": true,
|
||||||
|
"namedChunks": false,
|
||||||
|
"aot": true,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "ts:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "ts:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "ts:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "src/test.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "src/tsconfig.spec.json",
|
||||||
|
"karmaConfig": "src/karma.conf.js",
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/assets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [
|
||||||
|
"src/tsconfig.app.json",
|
||||||
|
"src/tsconfig.spec.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ts-e2e": {
|
||||||
|
"root": "e2e/",
|
||||||
|
"projectType": "application",
|
||||||
|
"architect": {
|
||||||
|
"e2e": {
|
||||||
|
"builder": "@angular-devkit/build-angular:protractor",
|
||||||
|
"options": {
|
||||||
|
"protractorConfig": "e2e/protractor.conf.js",
|
||||||
|
"devServerTarget": "ts:serve"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"devServerTarget": "ts:serve:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": "e2e/tsconfig.e2e.json",
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "ts"
|
||||||
|
}
|
||||||
28
src/main/ts/e2e/protractor.conf.js
Normal file
28
src/main/ts/e2e/protractor.conf.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Protractor configuration file, see link for more information
|
||||||
|
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||||
|
|
||||||
|
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||||
|
|
||||||
|
exports.config = {
|
||||||
|
allScriptsTimeout: 11000,
|
||||||
|
specs: [
|
||||||
|
'./src/**/*.e2e-spec.ts'
|
||||||
|
],
|
||||||
|
capabilities: {
|
||||||
|
'browserName': 'chrome'
|
||||||
|
},
|
||||||
|
directConnect: true,
|
||||||
|
baseUrl: 'http://localhost:4200/',
|
||||||
|
framework: 'jasmine',
|
||||||
|
jasmineNodeOpts: {
|
||||||
|
showColors: true,
|
||||||
|
defaultTimeoutInterval: 30000,
|
||||||
|
print: function() {}
|
||||||
|
},
|
||||||
|
onPrepare() {
|
||||||
|
require('ts-node').register({
|
||||||
|
project: require('path').join(__dirname, './tsconfig.e2e.json')
|
||||||
|
});
|
||||||
|
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||||
|
}
|
||||||
|
};
|
||||||
14
src/main/ts/e2e/src/app.e2e-spec.ts
Normal file
14
src/main/ts/e2e/src/app.e2e-spec.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { AppPage } from './app.po';
|
||||||
|
|
||||||
|
describe('workspace-project App', () => {
|
||||||
|
let page: AppPage;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
page = new AppPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display welcome message', () => {
|
||||||
|
page.navigateTo();
|
||||||
|
expect(page.getParagraphText()).toEqual('Welcome to ts!');
|
||||||
|
});
|
||||||
|
});
|
||||||
11
src/main/ts/e2e/src/app.po.ts
Normal file
11
src/main/ts/e2e/src/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { browser, by, element } from 'protractor';
|
||||||
|
|
||||||
|
export class AppPage {
|
||||||
|
navigateTo() {
|
||||||
|
return browser.get('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
getParagraphText() {
|
||||||
|
return element(by.css('app-root h1')).getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/ts/e2e/tsconfig.e2e.json
Normal file
13
src/main/ts/e2e/tsconfig.e2e.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/app",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"jasminewd2",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/ts/package.json
Normal file
55
src/main/ts/package.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "ts",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint",
|
||||||
|
"e2e": "ng e2e"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^6.1.0",
|
||||||
|
"@angular/common": "^6.1.0",
|
||||||
|
"@angular/compiler": "^6.1.0",
|
||||||
|
"@angular/core": "^6.1.0",
|
||||||
|
"@angular/forms": "^6.1.0",
|
||||||
|
"@angular/http": "^6.1.0",
|
||||||
|
"@angular/platform-browser": "^6.1.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^6.1.0",
|
||||||
|
"@angular/router": "^6.1.0",
|
||||||
|
"@ngtools/webpack": "^1.2.4",
|
||||||
|
"@types/chart.js": "^2.7.36",
|
||||||
|
"angular-bootstrap-md": "^6.2.4",
|
||||||
|
"angular5-csv": "^0.2.10",
|
||||||
|
"chart.js": "^2.5.0",
|
||||||
|
"core-js": "^2.5.4",
|
||||||
|
"font-awesome": "^4.7.0",
|
||||||
|
"hammerjs": "^2.0.8",
|
||||||
|
"rxjs": "~6.2.0",
|
||||||
|
"zone.js": "~0.8.26"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "~0.8.0",
|
||||||
|
"@angular/cli": "~6.2.3",
|
||||||
|
"@angular/compiler-cli": "^6.1.0",
|
||||||
|
"@angular/language-service": "^6.1.0",
|
||||||
|
"@types/jasmine": "~2.8.8",
|
||||||
|
"@types/jasminewd2": "~2.0.3",
|
||||||
|
"@types/node": "~8.9.4",
|
||||||
|
"codelyzer": "~4.3.0",
|
||||||
|
"jasmine-core": "~2.99.1",
|
||||||
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
|
"karma": "~3.0.0",
|
||||||
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
|
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||||
|
"karma-jasmine": "~1.1.2",
|
||||||
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
|
"protractor": "~5.4.0",
|
||||||
|
"ts-node": "~7.0.0",
|
||||||
|
"tslint": "~5.11.0",
|
||||||
|
"typescript": "~2.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/main/ts/src/app/app.component.html
Normal file
6
src/main/ts/src/app/app.component.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<app-header></app-header>
|
||||||
|
<main class="container">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</main>
|
||||||
|
<!-- <app-footer></app-footer> -->
|
||||||
|
|
||||||
4
src/main/ts/src/app/app.component.scss
Normal file
4
src/main/ts/src/app/app.component.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
main {
|
||||||
|
margin-top: 84px;
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
27
src/main/ts/src/app/app.component.spec.ts
Normal file
27
src/main/ts/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
it('should create the app', async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.debugElement.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
}));
|
||||||
|
it(`should have as title 'ts'`, async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.debugElement.componentInstance;
|
||||||
|
expect(app.title).toEqual('ts');
|
||||||
|
}));
|
||||||
|
it('should render title in a h1 tag', async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.debugElement.nativeElement;
|
||||||
|
expect(compiled.querySelector('h1').textContent).toContain('Welcome to ts!');
|
||||||
|
}));
|
||||||
|
});
|
||||||
10
src/main/ts/src/app/app.component.ts
Normal file
10
src/main/ts/src/app/app.component.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
title = 'app';
|
||||||
|
}
|
||||||
68
src/main/ts/src/app/app.module.ts
Normal file
68
src/main/ts/src/app/app.module.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// **********************************************
|
||||||
|
// Angular Core
|
||||||
|
// **********************************************
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
|
|
||||||
|
// **********************************************
|
||||||
|
// External dependencies
|
||||||
|
// **********************************************
|
||||||
|
import { MDBBootstrapModule } from 'angular-bootstrap-md';
|
||||||
|
|
||||||
|
// **********************************************
|
||||||
|
// App Components
|
||||||
|
// **********************************************
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { HeaderComponent } from './header/header.component';
|
||||||
|
import { ServerComponent } from './server/server.component';
|
||||||
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { DisconnectionComponent } from './disconnection/disconnection.component';
|
||||||
|
|
||||||
|
// **********************************************
|
||||||
|
// App Services
|
||||||
|
// **********************************************
|
||||||
|
import { ServerService } from './server/server.service';
|
||||||
|
import { LoginService } from './login/login.service';
|
||||||
|
import { AuthService } from './core/services/auth.service';
|
||||||
|
|
||||||
|
// Router
|
||||||
|
import { appRoutes } from './app.routes';
|
||||||
|
|
||||||
|
// Interceptor
|
||||||
|
import { TokenInterceptor } from './core/interceptors/token-interceptor';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
HeaderComponent,
|
||||||
|
ServerComponent,
|
||||||
|
LoginComponent,
|
||||||
|
DisconnectionComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
HttpClientModule,
|
||||||
|
FormsModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
appRoutes,
|
||||||
|
// { enableTracing: true } // Enabling tracing
|
||||||
|
{ onSameUrlNavigation: 'reload' }
|
||||||
|
),
|
||||||
|
MDBBootstrapModule.forRoot()
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ServerService,
|
||||||
|
LoginService,
|
||||||
|
AuthService,
|
||||||
|
{
|
||||||
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
useClass: TokenInterceptor,
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
15
src/main/ts/src/app/app.routes.ts
Normal file
15
src/main/ts/src/app/app.routes.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { DisconnectionComponent } from './disconnection/disconnection.component';
|
||||||
|
import { ServerComponent } from './server/server.component';
|
||||||
|
|
||||||
|
// import { AuthGuard } from './core/guards/auth.guard';
|
||||||
|
|
||||||
|
export const appRoutes: Routes = [
|
||||||
|
{ path: 'login', component: LoginComponent },
|
||||||
|
{ path: 'disconnection', component: DisconnectionComponent },
|
||||||
|
{ path: 'server', component: ServerComponent },
|
||||||
|
{ path: '', redirectTo: '/server', pathMatch: 'full' },
|
||||||
|
{ path: '**', redirectTo: '/server', pathMatch: 'full' }
|
||||||
|
];
|
||||||
10
src/main/ts/src/app/core/entities.ts
Normal file
10
src/main/ts/src/app/core/entities.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export class User {
|
||||||
|
constructor(
|
||||||
|
public key: string,
|
||||||
|
public name: string,
|
||||||
|
public email: string,
|
||||||
|
public password: string,
|
||||||
|
public inscriptionDate: Date,
|
||||||
|
public token: string
|
||||||
|
) { }
|
||||||
|
}
|
||||||
45
src/main/ts/src/app/core/interceptors/token-interceptor.ts
Normal file
45
src/main/ts/src/app/core/interceptors/token-interceptor.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, filter, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { User } from '../entities';
|
||||||
|
import { AuthService } from '../services/auth.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TokenInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
const token = this.authService.getToken();
|
||||||
|
|
||||||
|
let request: HttpRequest<any> = req;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
request = req.clone({
|
||||||
|
setHeaders: {
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next.handle(request).pipe(
|
||||||
|
tap(event => {
|
||||||
|
// Do nothing for the interceptor
|
||||||
|
}, (err: any) => {
|
||||||
|
if (err instanceof HttpErrorResponse && err.status === 401) {
|
||||||
|
this.authService.disconnect();
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
39
src/main/ts/src/app/core/services/auth.service.ts
Normal file
39
src/main/ts/src/app/core/services/auth.service.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { User } from '../entities';
|
||||||
|
|
||||||
|
const PARAM_TOKEN = 'token';
|
||||||
|
const PARAM_USER = 'user';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public getToken(): string {
|
||||||
|
return localStorage.getItem(PARAM_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setToken(token: string): void {
|
||||||
|
localStorage.setItem(PARAM_TOKEN, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAuthenticated(): boolean {
|
||||||
|
return this.getToken() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnect(): void {
|
||||||
|
localStorage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAdmin(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setUser(user: User): void {
|
||||||
|
localStorage.setItem(PARAM_USER, JSON.stringify(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUser(): User {
|
||||||
|
return JSON.parse(localStorage.getItem(PARAM_USER));
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/ts/src/app/disconnection/disconnection.component.ts
Normal file
21
src/main/ts/src/app/disconnection/disconnection.component.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { AuthService } from '../core/services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-disconnection',
|
||||||
|
template: 'Déconnexion...'
|
||||||
|
})
|
||||||
|
export class DisconnectionComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.authService.disconnect();
|
||||||
|
this.router.navigate(['/server']);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/ts/src/app/header/header.component.html
Normal file
27
src/main/ts/src/app/header/header.component.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<mdb-navbar SideClass="navbar fixed-top navbar-expand-lg navbar-dark green scrolling-navbar ie-nav" [containerInside]="false">
|
||||||
|
<logo>
|
||||||
|
<a class="logo navbar-brand" routerLink="/">
|
||||||
|
<img id="logo" src="./assets/favicon.png" alt="logo" />
|
||||||
|
<span id="title" *ngIf="!title">Minager</span>
|
||||||
|
<span id="title" *ngIf="title">Minager - {{title}}</span>
|
||||||
|
</a>
|
||||||
|
</logo>
|
||||||
|
<links>
|
||||||
|
<ul class="navbar-nav ml-auto nav-flex-icons" style="margin-left: 0 !important;">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a routerLink="/login"
|
||||||
|
*ngIf="!isAuthenticated()"
|
||||||
|
class="nav-link waves-light"
|
||||||
|
mdbRippleRadius>
|
||||||
|
<i class="fa fa-sign-in"></i> Connexion
|
||||||
|
</a>
|
||||||
|
<a routerLink="/disconnection"
|
||||||
|
*ngIf="isAuthenticated()"
|
||||||
|
class="nav-link waves-light danger-color-dark"
|
||||||
|
mdbRippleRadius >
|
||||||
|
<i class="fa fa-sign-out"></i> Déconnexion
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</links>
|
||||||
|
</mdb-navbar>
|
||||||
22
src/main/ts/src/app/header/header.component.ts
Normal file
22
src/main/ts/src/app/header/header.component.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { AuthService } from '../core/services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
templateUrl: './header.component.html',
|
||||||
|
styles: [`
|
||||||
|
img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class HeaderComponent {
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return this.authService.isAuthenticated();
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/main/ts/src/app/login/login.component.html
Normal file
22
src/main/ts/src/app/login/login.component.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<div>
|
||||||
|
<label for="email">Adresse mail</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
[(ngModel)]="model.email" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
[(ngModel)]="model.password" />
|
||||||
|
</div>
|
||||||
|
<button (click)="submitLogin()">Suivant</button>
|
||||||
|
<div id="errorMsg" class="card red lighten-2 text-center z-depth-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="white-text mb-0">{{loginError}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
60
src/main/ts/src/app/login/login.component.ts
Normal file
60
src/main/ts/src/app/login/login.component.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { User } from '../core/entities';
|
||||||
|
import { LoginService } from './login.service';
|
||||||
|
import { AuthService } from '../core/services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: 'login.component.html',
|
||||||
|
styles: [`
|
||||||
|
#form {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitFormArea {
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errorMsg {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.5s ease-out;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
model: User = new User('', '', '', '', undefined, '');
|
||||||
|
loginError;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private loginService: LoginService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
submitLogin(): void {
|
||||||
|
this.loginService.login(this.model).subscribe(user => {
|
||||||
|
this.authService.setToken(user.token);
|
||||||
|
this.authService.setUser(user);
|
||||||
|
this.router.navigate(['/server']);
|
||||||
|
}, error => {
|
||||||
|
this.setMessage('Email ou password incorrect.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage(message: string): void {
|
||||||
|
this.loginError = message;
|
||||||
|
|
||||||
|
const resultMsgDiv = document.getElementById('errorMsg');
|
||||||
|
resultMsgDiv.style.maxHeight = '64px';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resultMsgDiv.style.maxHeight = '0px';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loginError = undefined;
|
||||||
|
}, 550);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/main/ts/src/app/login/login.service.ts
Normal file
22
src/main/ts/src/app/login/login.service.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
import { User } from '../core/entities';
|
||||||
|
|
||||||
|
const LOGIN_URL = environment.apiUrl + '/api/account/login';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoginService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
login(user: User): Observable<User> {
|
||||||
|
return this.http.post<User>(LOGIN_URL, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/ts/src/app/server/server.component.html
Normal file
46
src/main/ts/src/app/server/server.component.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<div>
|
||||||
|
Status du serveur :
|
||||||
|
<span *ngIf="!serverStartedChecked" class="badge badge-warning">
|
||||||
|
Vérification
|
||||||
|
</span>
|
||||||
|
<span *ngIf="serverStartedChecked" class="badge"
|
||||||
|
[ngClass]="serverStarted ? 'badge-success' : 'badge-danger'">
|
||||||
|
{{serverStarted ? 'Démarré' : 'Éteint'}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="isAuthenticated()">
|
||||||
|
<button mdbBtn
|
||||||
|
*ngIf="serverStarted"
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
class="waves-light"
|
||||||
|
[disabled]="restarting"
|
||||||
|
(click)="restartServer()"
|
||||||
|
mdbWavesEffect>
|
||||||
|
Redémarrer
|
||||||
|
</button>
|
||||||
|
<button mdbBtn
|
||||||
|
type="button"
|
||||||
|
class="waves-light"
|
||||||
|
[ngClass]="serverStarted ? 'btn-danger' : 'btn-success'"
|
||||||
|
[disabled]="restarting"
|
||||||
|
(click)="startOrStopServer()"
|
||||||
|
mdbWavesEffect>
|
||||||
|
{{serverStarted ? 'Éteindre' : 'Démarrer'}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="errorMsg" class="msg card red lighten-2 text-center z-depth-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="white-text mb-0">{{errorMsg}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="warnMsg" class="msg card orange lighten-2 text-center z-depth-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="white-text mb-0">{{warnMsg}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="successMsg" class="msg card green lighten-2 text-center z-depth-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="white-text mb-0">{{successMsg}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
85
src/main/ts/src/app/server/server.component.ts
Normal file
85
src/main/ts/src/app/server/server.component.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ServerService } from './server.service';
|
||||||
|
import { AuthService } from '../core/services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-server',
|
||||||
|
templateUrl: 'server.component.html',
|
||||||
|
styles: [`
|
||||||
|
.msg {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.5s ease-out;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class ServerComponent implements OnInit {
|
||||||
|
serverStartedChecked = false;
|
||||||
|
serverStarted = false;
|
||||||
|
errorMsg;
|
||||||
|
warnMsg;
|
||||||
|
successMsg;
|
||||||
|
restarting = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private serverService: ServerService,
|
||||||
|
private authService: AuthService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.serverService.getStatus().subscribe(pServerStatus => {
|
||||||
|
this.serverStartedChecked = true;
|
||||||
|
this.serverStarted = pServerStatus === 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return this.authService.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
restartServer(): void {
|
||||||
|
this.restarting = true;
|
||||||
|
|
||||||
|
this.serverService.restartServer().subscribe(() => {
|
||||||
|
this.setMessage('Serveur redémarré.', 'successMsg');
|
||||||
|
}, error => {
|
||||||
|
this.setMessage('Une erreur est survenue lors du redémarrage du serveur.',
|
||||||
|
'errorMsg');
|
||||||
|
}, () => {
|
||||||
|
this.restarting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startOrStopServer(): void {
|
||||||
|
this.serverService[(this.serverStarted ? 'stop' : 'start') + 'Server']().subscribe(() => {
|
||||||
|
this.setMessage('Serveur ' + (this.serverStarted ? 'éteint' : 'démarré') + '.', 'successMsg');
|
||||||
|
this.serverStarted = !this.serverStarted;
|
||||||
|
}, error => {
|
||||||
|
if (error.status === 409) {
|
||||||
|
this.setMessage('Le serveur est déjà '
|
||||||
|
+ (this.serverStarted ? 'éteint' : 'démarré') + '.',
|
||||||
|
'warnMsg');
|
||||||
|
} else {
|
||||||
|
this.setMessage('Une erreur est survenue lors '
|
||||||
|
+ (this.serverStarted ? 'de l\'extinction' : 'du démarrage')
|
||||||
|
+ 'du serveur.',
|
||||||
|
'errorMsg');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage(message: string, type: string): void {
|
||||||
|
this[type] = message;
|
||||||
|
|
||||||
|
const resultMsgDiv = document.getElementById(type);
|
||||||
|
resultMsgDiv.style.maxHeight = '64px';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resultMsgDiv.style.maxHeight = '0px';
|
||||||
|
setTimeout(() => {
|
||||||
|
this[type] = undefined;
|
||||||
|
}, 550);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/ts/src/app/server/server.service.ts
Normal file
29
src/main/ts/src/app/server/server.service.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
const SERVER_URL = environment.apiUrl + '/api/server';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ServerService {
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getStatus(): Observable<number> {
|
||||||
|
return this.http.get<number>(`${SERVER_URL}/status`);
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer(): Observable<any> {
|
||||||
|
return this.http.post(`${SERVER_URL}/start`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopServer(): Observable<any> {
|
||||||
|
return this.http.post(`${SERVER_URL}/stop`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
restartServer(): Observable<any> {
|
||||||
|
return this.http.post(`${SERVER_URL}/restart`, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/main/ts/src/assets/favicon.png
Normal file
BIN
src/main/ts/src/assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
11
src/main/ts/src/browserslist
Normal file
11
src/main/ts/src/browserslist
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
|
||||||
|
# For additional information regarding the format and rule options, please see:
|
||||||
|
# https://github.com/browserslist/browserslist#queries
|
||||||
|
#
|
||||||
|
# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
|
||||||
|
|
||||||
|
> 0.5%
|
||||||
|
last 2 versions
|
||||||
|
Firefox ESR
|
||||||
|
not dead
|
||||||
|
not IE 9-11
|
||||||
3
src/main/ts/src/environments/environment.prod.ts
Normal file
3
src/main/ts/src/environments/environment.prod.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const environment = {
|
||||||
|
production: true
|
||||||
|
};
|
||||||
19
src/main/ts/src/environments/environment.ts
Normal file
19
src/main/ts/src/environments/environment.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// This file can be replaced during build by using the `fileReplacements` array.
|
||||||
|
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||||
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
|
export const environment = {
|
||||||
|
production: false,
|
||||||
|
apiUrl: 'http://localhost:8080',
|
||||||
|
appVersion: '0.0.1',
|
||||||
|
title: 'LOCAL'
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For easier debugging in development mode, you can import the following file
|
||||||
|
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||||
|
*
|
||||||
|
* This import should be commented out in production mode because it will have a negative impact
|
||||||
|
* on performance if an error is thrown.
|
||||||
|
*/
|
||||||
|
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||||
BIN
src/main/ts/src/favicon.ico
Normal file
BIN
src/main/ts/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/main/ts/src/favicon.png
Normal file
BIN
src/main/ts/src/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
14
src/main/ts/src/index.html
Normal file
14
src/main/ts/src/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<title>Minager</title>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<base href="/" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.png">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
src/main/ts/src/karma.conf.js
Normal file
31
src/main/ts/src/karma.conf.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage-istanbul-reporter'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
coverageIstanbulReporter: {
|
||||||
|
dir: require('path').join(__dirname, '../coverage'),
|
||||||
|
reports: ['html', 'lcovonly'],
|
||||||
|
fixWebpackSourcePaths: true
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false
|
||||||
|
});
|
||||||
|
};
|
||||||
13
src/main/ts/src/main.ts
Normal file
13
src/main/ts/src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { enableProdMode } from '@angular/core';
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
import { environment } from './environments/environment';
|
||||||
|
|
||||||
|
if (environment.production) {
|
||||||
|
enableProdMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
|
||||||
80
src/main/ts/src/polyfills.ts
Normal file
80
src/main/ts/src/polyfills.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||||
|
* You can add your own extra polyfills to this file.
|
||||||
|
*
|
||||||
|
* This file is divided into 2 sections:
|
||||||
|
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||||
|
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||||
|
* file.
|
||||||
|
*
|
||||||
|
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||||
|
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||||
|
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||||
|
*
|
||||||
|
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* BROWSER POLYFILLS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||||
|
// import 'core-js/es6/symbol';
|
||||||
|
// import 'core-js/es6/object';
|
||||||
|
// import 'core-js/es6/function';
|
||||||
|
// import 'core-js/es6/parse-int';
|
||||||
|
// import 'core-js/es6/parse-float';
|
||||||
|
// import 'core-js/es6/number';
|
||||||
|
// import 'core-js/es6/math';
|
||||||
|
// import 'core-js/es6/string';
|
||||||
|
// import 'core-js/es6/date';
|
||||||
|
// import 'core-js/es6/array';
|
||||||
|
// import 'core-js/es6/regexp';
|
||||||
|
// import 'core-js/es6/map';
|
||||||
|
// import 'core-js/es6/weak-map';
|
||||||
|
// import 'core-js/es6/set';
|
||||||
|
|
||||||
|
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||||
|
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||||
|
|
||||||
|
/** IE10 and IE11 requires the following for the Reflect API. */
|
||||||
|
// import 'core-js/es6/reflect';
|
||||||
|
|
||||||
|
|
||||||
|
/** Evergreen browsers require these. **/
|
||||||
|
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
|
||||||
|
import 'core-js/es7/reflect';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Animations `@angular/platform-browser/animations`
|
||||||
|
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||||
|
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||||
|
**/
|
||||||
|
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||||
|
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||||
|
*/
|
||||||
|
|
||||||
|
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||||
|
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||||
|
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||||
|
|
||||||
|
/*
|
||||||
|
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||||
|
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||||
|
*/
|
||||||
|
// (window as any).__Zone_enable_cross_context_check = true;
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Zone JS is required by default for Angular itself.
|
||||||
|
*/
|
||||||
|
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
* APPLICATION IMPORTS
|
||||||
|
*/
|
||||||
1
src/main/ts/src/styles.scss
Normal file
1
src/main/ts/src/styles.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
20
src/main/ts/src/test.ts
Normal file
20
src/main/ts/src/test.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||||
|
|
||||||
|
import 'zone.js/dist/zone-testing';
|
||||||
|
import { getTestBed } from '@angular/core/testing';
|
||||||
|
import {
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting
|
||||||
|
} from '@angular/platform-browser-dynamic/testing';
|
||||||
|
|
||||||
|
declare const require: any;
|
||||||
|
|
||||||
|
// First, initialize the Angular testing environment.
|
||||||
|
getTestBed().initTestEnvironment(
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting()
|
||||||
|
);
|
||||||
|
// Then we find all the tests.
|
||||||
|
const context = require.context('./', true, /\.spec\.ts$/);
|
||||||
|
// And load the modules.
|
||||||
|
context.keys().map(context);
|
||||||
11
src/main/ts/src/tsconfig.app.json
Normal file
11
src/main/ts/src/tsconfig.app.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
18
src/main/ts/src/tsconfig.spec.json
Normal file
18
src/main/ts/src/tsconfig.spec.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"test.ts",
|
||||||
|
"polyfills.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
17
src/main/ts/src/tslint.json
Normal file
17
src/main/ts/src/tslint.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"directive-selector": [
|
||||||
|
true,
|
||||||
|
"attribute",
|
||||||
|
"app",
|
||||||
|
"camelCase"
|
||||||
|
],
|
||||||
|
"component-selector": [
|
||||||
|
true,
|
||||||
|
"element",
|
||||||
|
"app",
|
||||||
|
"kebab-case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/ts/tsconfig.json
Normal file
21
src/main/ts/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"module": "es2015",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"target": "es5",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es2017",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
131
src/main/ts/tslint.json
Normal file
131
src/main/ts/tslint.json
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
{
|
||||||
|
"rulesDirectory": [
|
||||||
|
"node_modules/codelyzer"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"arrow-return-shorthand": true,
|
||||||
|
"callable-types": true,
|
||||||
|
"class-name": true,
|
||||||
|
"comment-format": [
|
||||||
|
true,
|
||||||
|
"check-space"
|
||||||
|
],
|
||||||
|
"curly": true,
|
||||||
|
"deprecation": {
|
||||||
|
"severity": "warn"
|
||||||
|
},
|
||||||
|
"eofline": true,
|
||||||
|
"forin": true,
|
||||||
|
"import-blacklist": [
|
||||||
|
true,
|
||||||
|
"rxjs/Rx"
|
||||||
|
],
|
||||||
|
"import-spacing": true,
|
||||||
|
"indent": [
|
||||||
|
true,
|
||||||
|
"spaces"
|
||||||
|
],
|
||||||
|
"interface-over-type-literal": true,
|
||||||
|
"label-position": true,
|
||||||
|
"max-line-length": [
|
||||||
|
true,
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"member-access": false,
|
||||||
|
"member-ordering": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
"static-field",
|
||||||
|
"instance-field",
|
||||||
|
"static-method",
|
||||||
|
"instance-method"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-arg": true,
|
||||||
|
"no-bitwise": true,
|
||||||
|
"no-console": [
|
||||||
|
true,
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"time",
|
||||||
|
"timeEnd",
|
||||||
|
"trace"
|
||||||
|
],
|
||||||
|
"no-construct": true,
|
||||||
|
"no-debugger": true,
|
||||||
|
"no-duplicate-super": true,
|
||||||
|
"no-empty": false,
|
||||||
|
"no-empty-interface": true,
|
||||||
|
"no-eval": true,
|
||||||
|
"no-inferrable-types": [
|
||||||
|
true,
|
||||||
|
"ignore-params"
|
||||||
|
],
|
||||||
|
"no-misused-new": true,
|
||||||
|
"no-non-null-assertion": true,
|
||||||
|
"no-redundant-jsdoc": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-string-literal": false,
|
||||||
|
"no-string-throw": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-trailing-whitespace": true,
|
||||||
|
"no-unnecessary-initializer": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-var-keyword": true,
|
||||||
|
"object-literal-sort-keys": false,
|
||||||
|
"one-line": [
|
||||||
|
true,
|
||||||
|
"check-open-brace",
|
||||||
|
"check-catch",
|
||||||
|
"check-else",
|
||||||
|
"check-whitespace"
|
||||||
|
],
|
||||||
|
"prefer-const": true,
|
||||||
|
"quotemark": [
|
||||||
|
true,
|
||||||
|
"single"
|
||||||
|
],
|
||||||
|
"radix": true,
|
||||||
|
"semicolon": [
|
||||||
|
true,
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"triple-equals": [
|
||||||
|
true,
|
||||||
|
"allow-null-check"
|
||||||
|
],
|
||||||
|
"typedef-whitespace": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unified-signatures": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": [
|
||||||
|
true,
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type"
|
||||||
|
],
|
||||||
|
"no-output-on-prefix": true,
|
||||||
|
"use-input-property-decorator": true,
|
||||||
|
"use-output-property-decorator": true,
|
||||||
|
"use-host-property-decorator": true,
|
||||||
|
"no-input-rename": true,
|
||||||
|
"no-output-rename": true,
|
||||||
|
"use-life-cycle-interface": true,
|
||||||
|
"use-pipe-transform-interface": true,
|
||||||
|
"component-class-suffix": true,
|
||||||
|
"directive-class-suffix": true
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/test/java/org/minager/MinagerApplicationTests.java
Normal file
16
src/test/java/org/minager/MinagerApplicationTests.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package org.minager;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest
|
||||||
|
public class MinagerApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user