existierendes front und back end
This commit is contained in:
		
						commit
						2fe26bb5ed
					
				|  | @ -0,0 +1,32 @@ | ||||||
|  | target/ | ||||||
|  | !.mvn/wrapper/maven-wrapper.jar | ||||||
|  | !**/src/main/**/target/ | ||||||
|  | !**/src/test/**/target/ | ||||||
|  | 
 | ||||||
|  | ### STS ### | ||||||
|  | .apt_generated | ||||||
|  | .classpath | ||||||
|  | .factorypath | ||||||
|  | .project | ||||||
|  | .settings | ||||||
|  | .springBeans | ||||||
|  | .sts4-cache | ||||||
|  | 
 | ||||||
|  | ### IntelliJ IDEA ### | ||||||
|  | .idea | ||||||
|  | *.iws | ||||||
|  | *.iml | ||||||
|  | *.ipr | ||||||
|  | 
 | ||||||
|  | ### NetBeans ### | ||||||
|  | /nbproject/private/ | ||||||
|  | /nbbuild/ | ||||||
|  | /dist/ | ||||||
|  | /nbdist/ | ||||||
|  | /.nb-gradle/ | ||||||
|  | build/ | ||||||
|  | !**/src/main/**/build/ | ||||||
|  | !**/src/test/**/build/ | ||||||
|  | 
 | ||||||
|  | ### VS Code ### | ||||||
|  | .vscode/ | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip | ||||||
|  | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar | ||||||
|  | @ -0,0 +1,52 @@ | ||||||
|  | # First part | ||||||
|  | #============ | ||||||
|  | 
 | ||||||
|  | # Start with an image containing JDK 17 | ||||||
|  | FROM openjdk:17-jdk-slim AS build | ||||||
|  | 
 | ||||||
|  | # Install Maven | ||||||
|  | RUN apt-get update && apt-get install -y --no-install-recommends maven git \ | ||||||
|  |     && apt-get clean \ | ||||||
|  |     && rm -rf /var/lib/apt/lists/* | ||||||
|  | 
 | ||||||
|  | # Setup working directory  | ||||||
|  | WORKDIR /App | ||||||
|  | 
 | ||||||
|  | # Copy the pom.xml file to the image | ||||||
|  | COPY ./pom.xml /App/ | ||||||
|  | 
 | ||||||
|  | # Cache maven dependencies | ||||||
|  | RUN mvn dependency:resolve | ||||||
|  | 
 | ||||||
|  | # Copy the entire src directory to the image | ||||||
|  | COPY ./src /App/src | ||||||
|  | 
 | ||||||
|  | # Build the app | ||||||
|  | RUN mvn clean package -Dmaven.test.skip=true | ||||||
|  | 
 | ||||||
|  | # Current dir is /App | ||||||
|  | # ==> You will find the result in folder target/  | ||||||
|  | 
 | ||||||
|  | # Second part | ||||||
|  | #============ | ||||||
|  | 
 | ||||||
|  | # start with a small image | ||||||
|  | FROM alpine:latest | ||||||
|  | 
 | ||||||
|  | # add JRE 17 | ||||||
|  | RUN apk add --no-cache openjdk17-jre | ||||||
|  | 
 | ||||||
|  | # Set working directory  | ||||||
|  | WORKDIR /App | ||||||
|  | 
 | ||||||
|  | # copy jar form first image to second image | ||||||
|  | COPY --from=build /App/target/*.jar /App/ | ||||||
|  | 
 | ||||||
|  | # db has to start for backend | ||||||
|  | COPY wait-for-db.sh /usr/local/bin/wait-for-db.sh | ||||||
|  | RUN chmod +x /usr/local/bin/wait-for-db.sh | ||||||
|  | 
 | ||||||
|  | # Expose Port | ||||||
|  | EXPOSE 8080 | ||||||
|  | 
 | ||||||
|  | ENTRYPOINT ["sh", "-c", "java -jar *.jar"] | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | # Backend for the _secret tresor application_ | ||||||
|  | 
 | ||||||
|  | This is the backend is a Java Springboot application. | ||||||
|  | 
 | ||||||
|  | It can be used by API-calls. | ||||||
|  | 
 | ||||||
|  | The data's are stored in a database. | ||||||
|  | 
 | ||||||
|  | ### information about database | ||||||
|  | 
 | ||||||
|  | prepare a database server and the database used by the application<br> | ||||||
|  | see [tresordb.sql](tresordb.sql) for an example database<br> | ||||||
|  | see [application.properties](src/main/resources/application.properties) about database access | ||||||
|  | 
 | ||||||
|  | ## Requests examples | ||||||
|  | 
 | ||||||
|  | see [UserRequests.http](httprequest/UserRequests.http)<br> | ||||||
|  | see [SecretRequests.http](httprequest/SecretRequests.http) | ||||||
|  | 
 | ||||||
|  | ## Environment variables | ||||||
|  | 
 | ||||||
|  | see [application.properties](src/main/resources/application.properties) | ||||||
|  | 
 | ||||||
|  | ## Build image | ||||||
|  | 
 | ||||||
|  | see Dockerfile | ||||||
|  | 
 | ||||||
|  | ```Bash | ||||||
|  | docker build -t tresorbackendimg . | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Start container local | ||||||
|  | 
 | ||||||
|  | ```Bash | ||||||
|  | docker run -p 8080:8080 --name tresorbackend tresorbackendimg | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | (c) P.Rutschmann | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | ### | ||||||
|  | #Get all secrets | ||||||
|  | GET http://localhost:8080/api/secrets | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | # Create new secret | ||||||
|  | POST http://localhost:8080/api/secrets | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "email": "hans.muster@bbw.ch", | ||||||
|  |   "content": { | ||||||
|  |     "kindid": 3, | ||||||
|  |     "kind": "note", | ||||||
|  |     "title": "Spaphira", | ||||||
|  |     "content": "Saphira roch an dem kleinen Erdling." | ||||||
|  |   }, | ||||||
|  |   "encryptPassword": "xxxyyy" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Post to get secrets with userid 1 and encryptpassword | ||||||
|  | POST http://localhost:8080/api/secrets/byuserid | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "userId": 1, | ||||||
|  |   "encryptPassword": "xxxyyy" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Post to get secrets with email and encryptpassword | ||||||
|  | POST http://localhost:8080/api/secrets/byemail | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "email": "hans.muster@bbw.ch", | ||||||
|  |   "encryptPassword": "xxxyyy" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | # Update secret with id 3 | ||||||
|  | PUT http://localhost:8080/api/secrets/3 | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "userId": 1, | ||||||
|  |   "content": { | ||||||
|  |     "kindid": 3, | ||||||
|  |     "kind": "note", | ||||||
|  |     "title": "Red drageon", | ||||||
|  |     "content": "Er war gewaltig und rot." | ||||||
|  |   }, | ||||||
|  |   "encryptPassword": "xxxyyy" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Delete secret with id 3 | ||||||
|  | DELETE http://localhost:8080/api/secrets/3 | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Delete secret with id 4 | ||||||
|  | DELETE http://localhost:8080/api/secrets/4 | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Delete secret with id 5 | ||||||
|  | DELETE http://localhost:8080/api/secrets/5 | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | ### | ||||||
|  | #Get all users | ||||||
|  | GET http://localhost:8080/api/users | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | # Create user Violett Weber | ||||||
|  | POST http://localhost:8080/api/users | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "firstName": "Violett", | ||||||
|  |   "lastName": "Weber", | ||||||
|  |   "email": "violett.weber@bbw.ch", | ||||||
|  |   "password": "1234", | ||||||
|  |   "passwordConfirmation": "1234" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Get user with id 4 | ||||||
|  | GET http://localhost:8080/api/users/4 | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | # Update user with id 4 | ||||||
|  | PUT http://localhost:8080/api/users/4 | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "firstName": "Iris", | ||||||
|  |   "lastName":"Weber", | ||||||
|  |   "email": "iris.weber@bbw.ch" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | #Delete user with id 4 | ||||||
|  | DELETE http://localhost:8080/api/users/4 | ||||||
|  | Accept: application/json | ||||||
|  | 
 | ||||||
|  | ### | ||||||
|  | # Post: get user by email | ||||||
|  | POST http://localhost:8080/api/users/byemail | ||||||
|  | Content-Type: application/json | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |   "email": "paula.kuster@bbw.ch" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,308 @@ | ||||||
|  | #!/bin/sh | ||||||
|  | # ---------------------------------------------------------------------------- | ||||||
|  | # Licensed to the Apache Software Foundation (ASF) under one | ||||||
|  | # or more contributor license agreements.  See the NOTICE file | ||||||
|  | # distributed with this work for additional information | ||||||
|  | # regarding copyright ownership.  The ASF licenses this file | ||||||
|  | # to you under the Apache License, Version 2.0 (the | ||||||
|  | # "License"); you may not use this file except in compliance | ||||||
|  | # with the License.  You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #    https://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, | ||||||
|  | # software distributed under the License is distributed on an | ||||||
|  | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||||
|  | # KIND, either express or implied.  See the License for the | ||||||
|  | # specific language governing permissions and limitations | ||||||
|  | # under the License. | ||||||
|  | # ---------------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | # ---------------------------------------------------------------------------- | ||||||
|  | # Apache Maven Wrapper startup batch script, version 3.2.0 | ||||||
|  | # | ||||||
|  | # Required ENV vars: | ||||||
|  | # ------------------ | ||||||
|  | #   JAVA_HOME - location of a JDK home dir | ||||||
|  | # | ||||||
|  | # Optional ENV vars | ||||||
|  | # ----------------- | ||||||
|  | #   MAVEN_OPTS - parameters passed to the Java VM when running Maven | ||||||
|  | #     e.g. to debug Maven itself, use | ||||||
|  | #       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 | ||||||
|  | #   MAVEN_SKIP_RC - flag to disable loading of mavenrc files | ||||||
|  | # ---------------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | if [ -z "$MAVEN_SKIP_RC" ] ; then | ||||||
|  | 
 | ||||||
|  |   if [ -f /usr/local/etc/mavenrc ] ; then | ||||||
|  |     . /usr/local/etc/mavenrc | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -f /etc/mavenrc ] ; then | ||||||
|  |     . /etc/mavenrc | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   if [ -f "$HOME/.mavenrc" ] ; then | ||||||
|  |     . "$HOME/.mavenrc" | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # OS specific support.  $var _must_ be set to either true or false. | ||||||
|  | cygwin=false; | ||||||
|  | darwin=false; | ||||||
|  | mingw=false | ||||||
|  | case "$(uname)" in | ||||||
|  |   CYGWIN*) cygwin=true ;; | ||||||
|  |   MINGW*) mingw=true;; | ||||||
|  |   Darwin*) darwin=true | ||||||
|  |     # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home | ||||||
|  |     # See https://developer.apple.com/library/mac/qa/qa1170/_index.html | ||||||
|  |     if [ -z "$JAVA_HOME" ]; then | ||||||
|  |       if [ -x "/usr/libexec/java_home" ]; then | ||||||
|  |         JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME | ||||||
|  |       else | ||||||
|  |         JAVA_HOME="/Library/Java/Home"; export JAVA_HOME | ||||||
|  |       fi | ||||||
|  |     fi | ||||||
|  |     ;; | ||||||
|  | esac | ||||||
|  | 
 | ||||||
|  | if [ -z "$JAVA_HOME" ] ; then | ||||||
|  |   if [ -r /etc/gentoo-release ] ; then | ||||||
|  |     JAVA_HOME=$(java-config --jre-home) | ||||||
|  |   fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # For Cygwin, ensure paths are in UNIX format before anything is touched | ||||||
|  | if $cygwin ; then | ||||||
|  |   [ -n "$JAVA_HOME" ] && | ||||||
|  |     JAVA_HOME=$(cygpath --unix "$JAVA_HOME") | ||||||
|  |   [ -n "$CLASSPATH" ] && | ||||||
|  |     CLASSPATH=$(cygpath --path --unix "$CLASSPATH") | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # For Mingw, ensure paths are in UNIX format before anything is touched | ||||||
|  | if $mingw ; then | ||||||
|  |   [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && | ||||||
|  |     JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -z "$JAVA_HOME" ]; then | ||||||
|  |   javaExecutable="$(which javac)" | ||||||
|  |   if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then | ||||||
|  |     # readlink(1) is not available as standard on Solaris 10. | ||||||
|  |     readLink=$(which readlink) | ||||||
|  |     if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then | ||||||
|  |       if $darwin ; then | ||||||
|  |         javaHome="$(dirname "\"$javaExecutable\"")" | ||||||
|  |         javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" | ||||||
|  |       else | ||||||
|  |         javaExecutable="$(readlink -f "\"$javaExecutable\"")" | ||||||
|  |       fi | ||||||
|  |       javaHome="$(dirname "\"$javaExecutable\"")" | ||||||
|  |       javaHome=$(expr "$javaHome" : '\(.*\)/bin') | ||||||
|  |       JAVA_HOME="$javaHome" | ||||||
|  |       export JAVA_HOME | ||||||
|  |     fi | ||||||
|  |   fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -z "$JAVACMD" ] ; then | ||||||
|  |   if [ -n "$JAVA_HOME"  ] ; then | ||||||
|  |     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||||||
|  |       # IBM's JDK on AIX uses strange locations for the executables | ||||||
|  |       JAVACMD="$JAVA_HOME/jre/sh/java" | ||||||
|  |     else | ||||||
|  |       JAVACMD="$JAVA_HOME/bin/java" | ||||||
|  |     fi | ||||||
|  |   else | ||||||
|  |     JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" | ||||||
|  |   fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ ! -x "$JAVACMD" ] ; then | ||||||
|  |   echo "Error: JAVA_HOME is not defined correctly." >&2 | ||||||
|  |   echo "  We cannot execute $JAVACMD" >&2 | ||||||
|  |   exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -z "$JAVA_HOME" ] ; then | ||||||
|  |   echo "Warning: JAVA_HOME environment variable is not set." | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # traverses directory structure from process work directory to filesystem root | ||||||
|  | # first directory with .mvn subdirectory is considered project base directory | ||||||
|  | find_maven_basedir() { | ||||||
|  |   if [ -z "$1" ] | ||||||
|  |   then | ||||||
|  |     echo "Path not specified to find_maven_basedir" | ||||||
|  |     return 1 | ||||||
|  |   fi | ||||||
|  | 
 | ||||||
|  |   basedir="$1" | ||||||
|  |   wdir="$1" | ||||||
|  |   while [ "$wdir" != '/' ] ; do | ||||||
|  |     if [ -d "$wdir"/.mvn ] ; then | ||||||
|  |       basedir=$wdir | ||||||
|  |       break | ||||||
|  |     fi | ||||||
|  |     # workaround for JBEAP-8937 (on Solaris 10/Sparc) | ||||||
|  |     if [ -d "${wdir}" ]; then | ||||||
|  |       wdir=$(cd "$wdir/.." || exit 1; pwd) | ||||||
|  |     fi | ||||||
|  |     # end of workaround | ||||||
|  |   done | ||||||
|  |   printf '%s' "$(cd "$basedir" || exit 1; pwd)" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # concatenates all lines of a file | ||||||
|  | concat_lines() { | ||||||
|  |   if [ -f "$1" ]; then | ||||||
|  |     # Remove \r in case we run on Windows within Git Bash | ||||||
|  |     # and check out the repository with auto CRLF management | ||||||
|  |     # enabled. Otherwise, we may read lines that are delimited with | ||||||
|  |     # \r\n and produce $'-Xarg\r' rather than -Xarg due to word | ||||||
|  |     # splitting rules. | ||||||
|  |     tr -s '\r\n' ' ' < "$1" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | log() { | ||||||
|  |   if [ "$MVNW_VERBOSE" = true ]; then | ||||||
|  |     printf '%s\n' "$1" | ||||||
|  |   fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") | ||||||
|  | if [ -z "$BASE_DIR" ]; then | ||||||
|  |   exit 1; | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR | ||||||
|  | log "$MAVEN_PROJECTBASEDIR" | ||||||
|  | 
 | ||||||
|  | ########################################################################################## | ||||||
|  | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central | ||||||
|  | # This allows using the maven wrapper in projects that prohibit checking in binary data. | ||||||
|  | ########################################################################################## | ||||||
|  | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" | ||||||
|  | if [ -r "$wrapperJarPath" ]; then | ||||||
|  |     log "Found $wrapperJarPath" | ||||||
|  | else | ||||||
|  |     log "Couldn't find $wrapperJarPath, downloading it ..." | ||||||
|  | 
 | ||||||
|  |     if [ -n "$MVNW_REPOURL" ]; then | ||||||
|  |       wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" | ||||||
|  |     else | ||||||
|  |       wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" | ||||||
|  |     fi | ||||||
|  |     while IFS="=" read -r key value; do | ||||||
|  |       # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) | ||||||
|  |       safeValue=$(echo "$value" | tr -d '\r') | ||||||
|  |       case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; | ||||||
|  |       esac | ||||||
|  |     done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" | ||||||
|  |     log "Downloading from: $wrapperUrl" | ||||||
|  | 
 | ||||||
|  |     if $cygwin; then | ||||||
|  |       wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     if command -v wget > /dev/null; then | ||||||
|  |         log "Found wget ... using wget" | ||||||
|  |         [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" | ||||||
|  |         if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then | ||||||
|  |             wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" | ||||||
|  |         else | ||||||
|  |             wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" | ||||||
|  |         fi | ||||||
|  |     elif command -v curl > /dev/null; then | ||||||
|  |         log "Found curl ... using curl" | ||||||
|  |         [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" | ||||||
|  |         if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then | ||||||
|  |             curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" | ||||||
|  |         else | ||||||
|  |             curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" | ||||||
|  |         fi | ||||||
|  |     else | ||||||
|  |         log "Falling back to using Java to download" | ||||||
|  |         javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" | ||||||
|  |         javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" | ||||||
|  |         # For Cygwin, switch paths to Windows format before running javac | ||||||
|  |         if $cygwin; then | ||||||
|  |           javaSource=$(cygpath --path --windows "$javaSource") | ||||||
|  |           javaClass=$(cygpath --path --windows "$javaClass") | ||||||
|  |         fi | ||||||
|  |         if [ -e "$javaSource" ]; then | ||||||
|  |             if [ ! -e "$javaClass" ]; then | ||||||
|  |                 log " - Compiling MavenWrapperDownloader.java ..." | ||||||
|  |                 ("$JAVA_HOME/bin/javac" "$javaSource") | ||||||
|  |             fi | ||||||
|  |             if [ -e "$javaClass" ]; then | ||||||
|  |                 log " - Running MavenWrapperDownloader.java ..." | ||||||
|  |                 ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  | fi | ||||||
|  | ########################################################################################## | ||||||
|  | # End of extension | ||||||
|  | ########################################################################################## | ||||||
|  | 
 | ||||||
|  | # If specified, validate the SHA-256 sum of the Maven wrapper jar file | ||||||
|  | wrapperSha256Sum="" | ||||||
|  | while IFS="=" read -r key value; do | ||||||
|  |   case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; | ||||||
|  |   esac | ||||||
|  | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" | ||||||
|  | if [ -n "$wrapperSha256Sum" ]; then | ||||||
|  |   wrapperSha256Result=false | ||||||
|  |   if command -v sha256sum > /dev/null; then | ||||||
|  |     if echo "$wrapperSha256Sum  $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then | ||||||
|  |       wrapperSha256Result=true | ||||||
|  |     fi | ||||||
|  |   elif command -v shasum > /dev/null; then | ||||||
|  |     if echo "$wrapperSha256Sum  $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then | ||||||
|  |       wrapperSha256Result=true | ||||||
|  |     fi | ||||||
|  |   else | ||||||
|  |     echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." | ||||||
|  |     echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." | ||||||
|  |     exit 1 | ||||||
|  |   fi | ||||||
|  |   if [ $wrapperSha256Result = false ]; then | ||||||
|  |     echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 | ||||||
|  |     echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 | ||||||
|  |     echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 | ||||||
|  |     exit 1 | ||||||
|  |   fi | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" | ||||||
|  | 
 | ||||||
|  | # For Cygwin, switch paths to Windows format before running java | ||||||
|  | if $cygwin; then | ||||||
|  |   [ -n "$JAVA_HOME" ] && | ||||||
|  |     JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") | ||||||
|  |   [ -n "$CLASSPATH" ] && | ||||||
|  |     CLASSPATH=$(cygpath --path --windows "$CLASSPATH") | ||||||
|  |   [ -n "$MAVEN_PROJECTBASEDIR" ] && | ||||||
|  |     MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # Provide a "standardized" way to retrieve the CLI args that will | ||||||
|  | # work with both Windows and non-Windows executions. | ||||||
|  | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" | ||||||
|  | export MAVEN_CMD_LINE_ARGS | ||||||
|  | 
 | ||||||
|  | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain | ||||||
|  | 
 | ||||||
|  | # shellcheck disable=SC2086 # safe args | ||||||
|  | exec "$JAVACMD" \ | ||||||
|  |   $MAVEN_OPTS \ | ||||||
|  |   $MAVEN_DEBUG_OPTS \ | ||||||
|  |   -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ | ||||||
|  |   "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ | ||||||
|  |   ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" | ||||||
|  | @ -0,0 +1,205 @@ | ||||||
|  | @REM ---------------------------------------------------------------------------- | ||||||
|  | @REM Licensed to the Apache Software Foundation (ASF) under one | ||||||
|  | @REM or more contributor license agreements.  See the NOTICE file | ||||||
|  | @REM distributed with this work for additional information | ||||||
|  | @REM regarding copyright ownership.  The ASF licenses this file | ||||||
|  | @REM to you under the Apache License, Version 2.0 (the | ||||||
|  | @REM "License"); you may not use this file except in compliance | ||||||
|  | @REM with the License.  You may obtain a copy of the License at | ||||||
|  | @REM | ||||||
|  | @REM    https://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | @REM | ||||||
|  | @REM Unless required by applicable law or agreed to in writing, | ||||||
|  | @REM software distributed under the License is distributed on an | ||||||
|  | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||||
|  | @REM KIND, either express or implied.  See the License for the | ||||||
|  | @REM specific language governing permissions and limitations | ||||||
|  | @REM under the License. | ||||||
|  | @REM ---------------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | @REM ---------------------------------------------------------------------------- | ||||||
|  | @REM Apache Maven Wrapper startup batch script, version 3.2.0 | ||||||
|  | @REM | ||||||
|  | @REM Required ENV vars: | ||||||
|  | @REM JAVA_HOME - location of a JDK home dir | ||||||
|  | @REM | ||||||
|  | @REM Optional ENV vars | ||||||
|  | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands | ||||||
|  | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending | ||||||
|  | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven | ||||||
|  | @REM     e.g. to debug Maven itself, use | ||||||
|  | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 | ||||||
|  | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files | ||||||
|  | @REM ---------------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' | ||||||
|  | @echo off | ||||||
|  | @REM set title of command window | ||||||
|  | title %0 | ||||||
|  | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' | ||||||
|  | @if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO% | ||||||
|  | 
 | ||||||
|  | @REM set %HOME% to equivalent of $HOME | ||||||
|  | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") | ||||||
|  | 
 | ||||||
|  | @REM Execute a user defined script before this one | ||||||
|  | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre | ||||||
|  | @REM check for pre script, once with legacy .bat ending and once with .cmd ending | ||||||
|  | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* | ||||||
|  | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* | ||||||
|  | :skipRcPre | ||||||
|  | 
 | ||||||
|  | @setlocal | ||||||
|  | 
 | ||||||
|  | set ERROR_CODE=0 | ||||||
|  | 
 | ||||||
|  | @REM To isolate internal variables from possible post scripts, we use another setlocal | ||||||
|  | @setlocal | ||||||
|  | 
 | ||||||
|  | @REM ==== START VALIDATION ==== | ||||||
|  | if not "%JAVA_HOME%" == "" goto OkJHome | ||||||
|  | 
 | ||||||
|  | echo. | ||||||
|  | echo Error: JAVA_HOME not found in your environment. >&2 | ||||||
|  | echo Please set the JAVA_HOME variable in your environment to match the >&2 | ||||||
|  | echo location of your Java installation. >&2 | ||||||
|  | echo. | ||||||
|  | goto error | ||||||
|  | 
 | ||||||
|  | :OkJHome | ||||||
|  | if exist "%JAVA_HOME%\bin\java.exe" goto init | ||||||
|  | 
 | ||||||
|  | echo. | ||||||
|  | echo Error: JAVA_HOME is set to an invalid directory. >&2 | ||||||
|  | echo JAVA_HOME = "%JAVA_HOME%" >&2 | ||||||
|  | echo Please set the JAVA_HOME variable in your environment to match the >&2 | ||||||
|  | echo location of your Java installation. >&2 | ||||||
|  | echo. | ||||||
|  | goto error | ||||||
|  | 
 | ||||||
|  | @REM ==== END VALIDATION ==== | ||||||
|  | 
 | ||||||
|  | :init | ||||||
|  | 
 | ||||||
|  | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". | ||||||
|  | @REM Fallback to current working directory if not found. | ||||||
|  | 
 | ||||||
|  | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% | ||||||
|  | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir | ||||||
|  | 
 | ||||||
|  | set EXEC_DIR=%CD% | ||||||
|  | set WDIR=%EXEC_DIR% | ||||||
|  | :findBaseDir | ||||||
|  | IF EXIST "%WDIR%"\.mvn goto baseDirFound | ||||||
|  | cd .. | ||||||
|  | IF "%WDIR%"=="%CD%" goto baseDirNotFound | ||||||
|  | set WDIR=%CD% | ||||||
|  | goto findBaseDir | ||||||
|  | 
 | ||||||
|  | :baseDirFound | ||||||
|  | set MAVEN_PROJECTBASEDIR=%WDIR% | ||||||
|  | cd "%EXEC_DIR%" | ||||||
|  | goto endDetectBaseDir | ||||||
|  | 
 | ||||||
|  | :baseDirNotFound | ||||||
|  | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% | ||||||
|  | cd "%EXEC_DIR%" | ||||||
|  | 
 | ||||||
|  | :endDetectBaseDir | ||||||
|  | 
 | ||||||
|  | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig | ||||||
|  | 
 | ||||||
|  | @setlocal EnableExtensions EnableDelayedExpansion | ||||||
|  | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a | ||||||
|  | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% | ||||||
|  | 
 | ||||||
|  | :endReadAdditionalConfig | ||||||
|  | 
 | ||||||
|  | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" | ||||||
|  | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" | ||||||
|  | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain | ||||||
|  | 
 | ||||||
|  | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" | ||||||
|  | 
 | ||||||
|  | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( | ||||||
|  |     IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central | ||||||
|  | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. | ||||||
|  | if exist %WRAPPER_JAR% ( | ||||||
|  |     if "%MVNW_VERBOSE%" == "true" ( | ||||||
|  |         echo Found %WRAPPER_JAR% | ||||||
|  |     ) | ||||||
|  | ) else ( | ||||||
|  |     if not "%MVNW_REPOURL%" == "" ( | ||||||
|  |         SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" | ||||||
|  |     ) | ||||||
|  |     if "%MVNW_VERBOSE%" == "true" ( | ||||||
|  |         echo Couldn't find %WRAPPER_JAR%, downloading it ... | ||||||
|  |         echo Downloading from: %WRAPPER_URL% | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     powershell -Command "&{"^ | ||||||
|  | 		"$webclient = new-object System.Net.WebClient;"^ | ||||||
|  | 		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ | ||||||
|  | 		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ | ||||||
|  | 		"}"^ | ||||||
|  | 		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ | ||||||
|  | 		"}" | ||||||
|  |     if "%MVNW_VERBOSE%" == "true" ( | ||||||
|  |         echo Finished downloading %WRAPPER_JAR% | ||||||
|  |     ) | ||||||
|  | ) | ||||||
|  | @REM End of extension | ||||||
|  | 
 | ||||||
|  | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file | ||||||
|  | SET WRAPPER_SHA_256_SUM="" | ||||||
|  | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( | ||||||
|  |     IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B | ||||||
|  | ) | ||||||
|  | IF NOT %WRAPPER_SHA_256_SUM%=="" ( | ||||||
|  |     powershell -Command "&{"^ | ||||||
|  |        "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ | ||||||
|  |        "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ | ||||||
|  |        "  Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ | ||||||
|  |        "  Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ | ||||||
|  |        "  Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ | ||||||
|  |        "  exit 1;"^ | ||||||
|  |        "}"^ | ||||||
|  |        "}" | ||||||
|  |     if ERRORLEVEL 1 goto error | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | @REM Provide a "standardized" way to retrieve the CLI args that will | ||||||
|  | @REM work with both Windows and non-Windows executions. | ||||||
|  | set MAVEN_CMD_LINE_ARGS=%* | ||||||
|  | 
 | ||||||
|  | %MAVEN_JAVA_EXE% ^ | ||||||
|  |   %JVM_CONFIG_MAVEN_PROPS% ^ | ||||||
|  |   %MAVEN_OPTS% ^ | ||||||
|  |   %MAVEN_DEBUG_OPTS% ^ | ||||||
|  |   -classpath %WRAPPER_JAR% ^ | ||||||
|  |   "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ | ||||||
|  |   %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* | ||||||
|  | if ERRORLEVEL 1 goto error | ||||||
|  | goto end | ||||||
|  | 
 | ||||||
|  | :error | ||||||
|  | set ERROR_CODE=1 | ||||||
|  | 
 | ||||||
|  | :end | ||||||
|  | @endlocal & set ERROR_CODE=%ERROR_CODE% | ||||||
|  | 
 | ||||||
|  | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost | ||||||
|  | @REM check for post script, once with legacy .bat ending and once with .cmd ending | ||||||
|  | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" | ||||||
|  | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" | ||||||
|  | :skipRcPost | ||||||
|  | 
 | ||||||
|  | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' | ||||||
|  | if "%MAVEN_BATCH_PAUSE%"=="on" pause | ||||||
|  | 
 | ||||||
|  | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% | ||||||
|  | 
 | ||||||
|  | cmd /C exit /B %ERROR_CODE% | ||||||
|  | @ -0,0 +1,80 @@ | ||||||
|  | <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||||
|  |     <modelVersion>4.0.0</modelVersion> | ||||||
|  |     <parent> | ||||||
|  |         <groupId>org.springframework.boot</groupId> | ||||||
|  |         <artifactId>spring-boot-starter-parent</artifactId> | ||||||
|  |         <version>3.2.4</version> | ||||||
|  |         <relativePath/> <!-- lookup parent from repository --> | ||||||
|  |     </parent> | ||||||
|  |     <groupId>ch.bbw.pr</groupId> | ||||||
|  |     <artifactId>tresorbackend</artifactId> | ||||||
|  |     <version>0.0.1-SNAPSHOT</version> | ||||||
|  |     <name>tresorbackend</name> | ||||||
|  |     <description>tresorbackend</description> | ||||||
|  |     <properties> | ||||||
|  |         <java.version>17</java.version> | ||||||
|  |         <maven.compiler.source>17</maven.compiler.source> | ||||||
|  |         <maven.compiler.target>17</maven.compiler.target> | ||||||
|  |     </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.mariadb.jdbc</groupId> | ||||||
|  |             <artifactId>mariadb-java-client</artifactId> | ||||||
|  |             <scope>runtime</scope> | ||||||
|  |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.mysql</groupId> | ||||||
|  |             <artifactId>mysql-connector-j</artifactId> | ||||||
|  |             <scope>runtime</scope> | ||||||
|  |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.projectlombok</groupId> | ||||||
|  |             <artifactId>lombok</artifactId> | ||||||
|  |             <optional>true</optional> | ||||||
|  |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework.boot</groupId> | ||||||
|  |             <artifactId>spring-boot-starter-test</artifactId> | ||||||
|  |             <scope>test</scope> | ||||||
|  |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework.boot</groupId> | ||||||
|  |             <artifactId>spring-boot-starter-validation</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.google.code.gson</groupId> | ||||||
|  |             <artifactId>gson</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework.security</groupId> | ||||||
|  |             <artifactId>spring-security-crypto</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |         <!-- https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.github.ulisesbocchio</groupId> | ||||||
|  |             <artifactId>jasypt-spring-boot-starter</artifactId> | ||||||
|  |             <version>3.0.5</version> | ||||||
|  |         </dependency> | ||||||
|  |     </dependencies> | ||||||
|  | 
 | ||||||
|  |     <build> | ||||||
|  |         <plugins> | ||||||
|  |             <plugin> | ||||||
|  |                 <groupId>org.springframework.boot</groupId> | ||||||
|  |                 <artifactId>spring-boot-maven-plugin</artifactId> | ||||||
|  |             </plugin> | ||||||
|  |         </plugins> | ||||||
|  |     </build> | ||||||
|  | 
 | ||||||
|  | </project> | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.ConfigProperties; | ||||||
|  | import org.springframework.boot.SpringApplication; | ||||||
|  | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||||
|  | import org.springframework.web.bind.annotation.CrossOrigin; | ||||||
|  | 
 | ||||||
|  | @SpringBootApplication | ||||||
|  | public class TresorbackendApplication { | ||||||
|  |    public static void main(String[] args) { | ||||||
|  |       SpringApplication.run(TresorbackendApplication.class, args); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,220 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.controller; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.Secret; | ||||||
|  | import ch.bbw.pr.tresorbackend.model.NewSecret; | ||||||
|  | import ch.bbw.pr.tresorbackend.model.EncryptCredentials; | ||||||
|  | import ch.bbw.pr.tresorbackend.model.User; | ||||||
|  | import ch.bbw.pr.tresorbackend.service.SecretService; | ||||||
|  | import ch.bbw.pr.tresorbackend.service.UserService; | ||||||
|  | import ch.bbw.pr.tresorbackend.util.EncryptUtil; | ||||||
|  | import com.google.gson.Gson; | ||||||
|  | import com.google.gson.JsonArray; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  | import jakarta.validation.Valid; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import org.jasypt.exceptions.EncryptionOperationNotPossibleException; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
|  | import org.springframework.validation.BindingResult; | ||||||
|  | import org.springframework.web.bind.annotation.*; | ||||||
|  | 
 | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SecretController | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @RestController | ||||||
|  | @AllArgsConstructor | ||||||
|  | @RequestMapping("api/secrets") | ||||||
|  | public class SecretController { | ||||||
|  | 
 | ||||||
|  |    private SecretService secretService; | ||||||
|  |    private UserService userService; | ||||||
|  | 
 | ||||||
|  |    // create secret REST API | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PostMapping | ||||||
|  |    public ResponseEntity<String> createSecret2(@Valid @RequestBody NewSecret newSecret, BindingResult bindingResult) { | ||||||
|  |       //input validation | ||||||
|  |       if (bindingResult.hasErrors()) { | ||||||
|  |          List<String> errors = bindingResult.getFieldErrors().stream() | ||||||
|  |                .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) | ||||||
|  |                .collect(Collectors.toList()); | ||||||
|  |          System.out.println("SecretController.createSecret " + errors); | ||||||
|  | 
 | ||||||
|  |          JsonArray arr = new JsonArray(); | ||||||
|  |          errors.forEach(arr::add); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.add("message", arr); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  | 
 | ||||||
|  |          System.out.println("SecretController.createSecret, validation fails: " + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  |       System.out.println("SecretController.createSecret, input validation passed"); | ||||||
|  | 
 | ||||||
|  |       User user = userService.findByEmail(newSecret.getEmail()); | ||||||
|  | 
 | ||||||
|  |       //transfer secret and encrypt content | ||||||
|  |       Secret secret = new Secret( | ||||||
|  |             null, | ||||||
|  |             user.getId(), | ||||||
|  |             new EncryptUtil(newSecret.getEncryptPassword()).encrypt(newSecret.getContent().toString()) | ||||||
|  |       ); | ||||||
|  |       //save secret in db | ||||||
|  |       secretService.createSecret(secret); | ||||||
|  |       System.out.println("SecretController.createSecret, secret saved in db"); | ||||||
|  |       JsonObject obj = new JsonObject(); | ||||||
|  |       obj.addProperty("answer", "Secret saved"); | ||||||
|  |       String json = new Gson().toJson(obj); | ||||||
|  |       System.out.println("SecretController.createSecret " + json); | ||||||
|  |       return ResponseEntity.accepted().body(json); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Get Secrets by userId REST API | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PostMapping("/byuserid") | ||||||
|  |    public ResponseEntity<List<Secret>> getSecretsByUserId(@RequestBody EncryptCredentials credentials) { | ||||||
|  |       System.out.println("SecretController.getSecretsByUserId " + credentials); | ||||||
|  | 
 | ||||||
|  |       List<Secret> secrets = secretService.getSecretsByUserId(credentials.getUserId()); | ||||||
|  |       if (secrets.isEmpty()) { | ||||||
|  |          System.out.println("SecretController.getSecretsByUserId secret isEmpty"); | ||||||
|  |          return ResponseEntity.notFound().build(); | ||||||
|  |       } | ||||||
|  |       //Decrypt content | ||||||
|  |       for(Secret secret: secrets) { | ||||||
|  |          try { | ||||||
|  |             secret.setContent(new EncryptUtil(credentials.getEncryptPassword()).decrypt(secret.getContent())); | ||||||
|  |          } catch (EncryptionOperationNotPossibleException e) { | ||||||
|  |             System.out.println("SecretController.getSecretsByUserId " + e + " " + secret); | ||||||
|  |             secret.setContent("not encryptable. Wrong password?"); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       System.out.println("SecretController.getSecretsByUserId " + secrets); | ||||||
|  |       return ResponseEntity.ok(secrets); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Get Secrets by email REST API | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PostMapping("/byemail") | ||||||
|  |    public ResponseEntity<List<Secret>> getSecretsByEmail(@RequestBody EncryptCredentials credentials) { | ||||||
|  |       System.out.println("SecretController.getSecretsByEmail " + credentials); | ||||||
|  | 
 | ||||||
|  |       User user = userService.findByEmail(credentials.getEmail()); | ||||||
|  | 
 | ||||||
|  |       List<Secret> secrets = secretService.getSecretsByUserId(user.getId()); | ||||||
|  |       if (secrets.isEmpty()) { | ||||||
|  |          System.out.println("SecretController.getSecretsByEmail secret isEmpty"); | ||||||
|  |          return ResponseEntity.notFound().build(); | ||||||
|  |       } | ||||||
|  |       //Decrypt content | ||||||
|  |       for(Secret secret: secrets) { | ||||||
|  |          try { | ||||||
|  |             secret.setContent(new EncryptUtil(credentials.getEncryptPassword()).decrypt(secret.getContent())); | ||||||
|  |          } catch (EncryptionOperationNotPossibleException e) { | ||||||
|  |             System.out.println("SecretController.getSecretsByEmail " + e + " " + secret); | ||||||
|  |             secret.setContent("not encryptable. Wrong password?"); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       System.out.println("SecretController.getSecretsByEmail " + secrets); | ||||||
|  |       return ResponseEntity.ok(secrets); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Get All Secrets REST API | ||||||
|  |    // http://localhost:8080/api/secrets | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @GetMapping | ||||||
|  |    public ResponseEntity<List<Secret>> getAllSecrets() { | ||||||
|  |       List<Secret> secrets = secretService.getAllSecrets(); | ||||||
|  |       return new ResponseEntity<>(secrets, HttpStatus.OK); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Update Secrete REST API | ||||||
|  |    // http://localhost:8080/api/secrets/1 | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PutMapping("{id}") | ||||||
|  |    public ResponseEntity<String> updateSecret( | ||||||
|  |          @PathVariable("id") Long secretId, | ||||||
|  |          @Valid @RequestBody NewSecret newSecret, | ||||||
|  |          BindingResult bindingResult) { | ||||||
|  |       //input validation | ||||||
|  |       if (bindingResult.hasErrors()) { | ||||||
|  |          List<String> errors = bindingResult.getFieldErrors().stream() | ||||||
|  |                .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) | ||||||
|  |                .collect(Collectors.toList()); | ||||||
|  |          System.out.println("SecretController.createSecret " + errors); | ||||||
|  | 
 | ||||||
|  |          JsonArray arr = new JsonArray(); | ||||||
|  |          errors.forEach(arr::add); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.add("message", arr); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  | 
 | ||||||
|  |          System.out.println("SecretController.updateSecret, validation fails: " + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       //get Secret with id | ||||||
|  |       Secret dbSecrete = secretService.getSecretById(secretId); | ||||||
|  |       if(dbSecrete == null){ | ||||||
|  |          System.out.println("SecretController.updateSecret, secret not found in db"); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.addProperty("answer", "Secret not found in db"); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  |          System.out.println("SecretController.updateSecret failed:" + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  |       User user = userService.findByEmail(newSecret.getEmail()); | ||||||
|  | 
 | ||||||
|  |       //check if Secret in db has not same userid | ||||||
|  |       if(dbSecrete.getUserId() != user.getId()){ | ||||||
|  |          System.out.println("SecretController.updateSecret, not same user id"); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.addProperty("answer", "Secret has not same user id"); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  |          System.out.println("SecretController.updateSecret failed:" + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  |       //check if Secret can be decrypted with password | ||||||
|  |       try { | ||||||
|  |          new EncryptUtil(newSecret.getEncryptPassword()).decrypt(dbSecrete.getContent()); | ||||||
|  |       } catch (EncryptionOperationNotPossibleException e) { | ||||||
|  |          System.out.println("SecretController.updateSecret, invalid password"); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.addProperty("answer", "Password not correct."); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  |          System.out.println("SecretController.updateSecret failed:" + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  |       //modify Secret in db. | ||||||
|  |       Secret secret = new Secret( | ||||||
|  |             secretId, | ||||||
|  |             user.getId(), | ||||||
|  |             new EncryptUtil(newSecret.getEncryptPassword()).encrypt(newSecret.getContent().toString()) | ||||||
|  |       ); | ||||||
|  |       Secret updatedSecret = secretService.updateSecret(secret); | ||||||
|  |       //save secret in db | ||||||
|  |       secretService.createSecret(secret); | ||||||
|  |       System.out.println("SecretController.updateSecret, secret updated in db"); | ||||||
|  |       JsonObject obj = new JsonObject(); | ||||||
|  |       obj.addProperty("answer", "Secret updated"); | ||||||
|  |       String json = new Gson().toJson(obj); | ||||||
|  |       System.out.println("SecretController.updateSecret " + json); | ||||||
|  |       return ResponseEntity.accepted().body(json); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Delete Secret REST API | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @DeleteMapping("{id}") | ||||||
|  |    public ResponseEntity<String> deleteSecret(@PathVariable("id") Long secretId) { | ||||||
|  |       //todo: Some kind of brute force delete, perhaps test first userid and encryptpassword | ||||||
|  |       secretService.deleteSecret(secretId); | ||||||
|  |       System.out.println("SecretController.deleteSecret succesfully: " + secretId); | ||||||
|  |       return new ResponseEntity<>("Secret successfully deleted!", HttpStatus.OK); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,181 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.controller; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.ConfigProperties; | ||||||
|  | import ch.bbw.pr.tresorbackend.model.EmailAdress; | ||||||
|  | import ch.bbw.pr.tresorbackend.model.RegisterUser; | ||||||
|  | import ch.bbw.pr.tresorbackend.model.User; | ||||||
|  | import ch.bbw.pr.tresorbackend.service.PasswordEncryptionService; | ||||||
|  | import ch.bbw.pr.tresorbackend.service.UserService; | ||||||
|  | 
 | ||||||
|  | import com.google.gson.Gson; | ||||||
|  | import com.google.gson.JsonArray; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  | import jakarta.validation.Valid; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
|  | import org.springframework.validation.BindingResult; | ||||||
|  | import org.springframework.web.bind.annotation.*; | ||||||
|  | 
 | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * UserController | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @RestController | ||||||
|  | @AllArgsConstructor | ||||||
|  | @RequestMapping("api/users") | ||||||
|  | public class UserController { | ||||||
|  | 
 | ||||||
|  |    private UserService userService; | ||||||
|  |    private PasswordEncryptionService passwordService; | ||||||
|  |    private final ConfigProperties configProperties; | ||||||
|  |    private static final Logger logger = LoggerFactory.getLogger(UserController.class); | ||||||
|  | 
 | ||||||
|  |    @Autowired | ||||||
|  |    public UserController(ConfigProperties configProperties, UserService userService, | ||||||
|  |                          PasswordEncryptionService passwordService) { | ||||||
|  |       this.configProperties = configProperties; | ||||||
|  |       System.out.println("UserController.UserController: cross origin: " + configProperties.getOrigin()); | ||||||
|  |       // Logging in the constructor | ||||||
|  |       logger.info("UserController initialized: " + configProperties.getOrigin()); | ||||||
|  |       logger.debug("UserController.UserController: Cross Origin Config: {}", configProperties.getOrigin()); | ||||||
|  |       this.userService = userService; | ||||||
|  |       this.passwordService = passwordService; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // build create User REST API | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PostMapping | ||||||
|  |    public ResponseEntity<String> createUser(@Valid @RequestBody RegisterUser registerUser, BindingResult bindingResult) { | ||||||
|  |       //captcha | ||||||
|  |       //todo ergänzen | ||||||
|  | 
 | ||||||
|  |       System.out.println("UserController.createUser: captcha passed."); | ||||||
|  | 
 | ||||||
|  |       //input validation | ||||||
|  |       if (bindingResult.hasErrors()) { | ||||||
|  |          List<String> errors = bindingResult.getFieldErrors().stream() | ||||||
|  |                .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) | ||||||
|  |                .collect(Collectors.toList()); | ||||||
|  |          System.out.println("UserController.createUser " + errors); | ||||||
|  | 
 | ||||||
|  |          JsonArray arr = new JsonArray(); | ||||||
|  |          errors.forEach(arr::add); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.add("message", arr); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  | 
 | ||||||
|  |          System.out.println("UserController.createUser, validation fails: " + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  |       System.out.println("UserController.createUser: input validation passed"); | ||||||
|  | 
 | ||||||
|  |       //password validation | ||||||
|  |       //todo ergänzen | ||||||
|  |       System.out.println("UserController.createUser, password validation passed"); | ||||||
|  | 
 | ||||||
|  |       //transform registerUser to user | ||||||
|  |       User user = new User( | ||||||
|  |             null, | ||||||
|  |             registerUser.getFirstName(), | ||||||
|  |             registerUser.getLastName(), | ||||||
|  |             registerUser.getEmail(), | ||||||
|  |             passwordService.hashPassword(registerUser.getPassword()) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |       User savedUser = userService.createUser(user); | ||||||
|  |       System.out.println("UserController.createUser, user saved in db"); | ||||||
|  |       JsonObject obj = new JsonObject(); | ||||||
|  |       obj.addProperty("answer", "User Saved"); | ||||||
|  |       String json = new Gson().toJson(obj); | ||||||
|  |       System.out.println("UserController.createUser " + json); | ||||||
|  |       return ResponseEntity.accepted().body(json); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // build get user by id REST API | ||||||
|  |    // http://localhost:8080/api/users/1 | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @GetMapping("{id}") | ||||||
|  |    public ResponseEntity<User> getUserById(@PathVariable("id") Long userId) { | ||||||
|  |       User user = userService.getUserById(userId); | ||||||
|  |       return new ResponseEntity<>(user, HttpStatus.OK); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Get All Users REST API | ||||||
|  |    // http://localhost:8080/api/users | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @GetMapping | ||||||
|  |    public ResponseEntity<List<User>> getAllUsers() { | ||||||
|  |       List<User> users = userService.getAllUsers(); | ||||||
|  |       return new ResponseEntity<>(users, HttpStatus.OK); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Update User REST API | ||||||
|  |    // http://localhost:8080/api/users/1 | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PutMapping("{id}") | ||||||
|  |    public ResponseEntity<User> updateUser(@PathVariable("id") Long userId, | ||||||
|  |                                           @RequestBody User user) { | ||||||
|  |       user.setId(userId); | ||||||
|  |       User updatedUser = userService.updateUser(user); | ||||||
|  |       return new ResponseEntity<>(updatedUser, HttpStatus.OK); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Build Delete User REST API | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @DeleteMapping("{id}") | ||||||
|  |    public ResponseEntity<String> deleteUser(@PathVariable("id") Long userId) { | ||||||
|  |       userService.deleteUser(userId); | ||||||
|  |       return new ResponseEntity<>("User successfully deleted!", HttpStatus.OK); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    // get user id by email | ||||||
|  |    @CrossOrigin(origins = "${CROSS_ORIGIN}") | ||||||
|  |    @PostMapping("/byemail") | ||||||
|  |    public ResponseEntity<String> getUserIdByEmail(@RequestBody EmailAdress email, BindingResult bindingResult) { | ||||||
|  |       System.out.println("UserController.getUserIdByEmail: " + email); | ||||||
|  |       //input validation | ||||||
|  |       if (bindingResult.hasErrors()) { | ||||||
|  |          List<String> errors = bindingResult.getFieldErrors().stream() | ||||||
|  |                .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) | ||||||
|  |                .collect(Collectors.toList()); | ||||||
|  |          System.out.println("UserController.createUser " + errors); | ||||||
|  | 
 | ||||||
|  |          JsonArray arr = new JsonArray(); | ||||||
|  |          errors.forEach(arr::add); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.add("message", arr); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  | 
 | ||||||
|  |          System.out.println("UserController.createUser, validation fails: " + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       System.out.println("UserController.getUserIdByEmail: input validation passed"); | ||||||
|  | 
 | ||||||
|  |       User user = userService.findByEmail(email.getEmail()); | ||||||
|  |       if (user == null) { | ||||||
|  |          System.out.println("UserController.getUserIdByEmail, no user found with email: " + email); | ||||||
|  |          JsonObject obj = new JsonObject(); | ||||||
|  |          obj.addProperty("message", "No user found with this email"); | ||||||
|  |          String json = new Gson().toJson(obj); | ||||||
|  | 
 | ||||||
|  |          System.out.println("UserController.getUserIdByEmail, fails: " + json); | ||||||
|  |          return ResponseEntity.badRequest().body(json); | ||||||
|  |       } | ||||||
|  |       System.out.println("UserController.getUserIdByEmail, user find by email"); | ||||||
|  |       JsonObject obj = new JsonObject(); | ||||||
|  |       obj.addProperty("answer", user.getId()); | ||||||
|  |       String json = new Gson().toJson(obj); | ||||||
|  |       System.out.println("UserController.getUserIdByEmail " + json); | ||||||
|  |       return ResponseEntity.accepted().body(json); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * ConfigProperties | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class ConfigProperties { | ||||||
|  | 
 | ||||||
|  |    @Value("${CROSS_ORIGIN}") | ||||||
|  |    private String crossOrigin; | ||||||
|  | 
 | ||||||
|  |    public String getOrigin() { | ||||||
|  |       return crossOrigin; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import jakarta.validation.constraints.NotEmpty; | ||||||
|  | import jakarta.validation.constraints.Size; | ||||||
|  | import lombok.*; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * EmailAdress | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @Setter | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class EmailAdress { | ||||||
|  |    @NotEmpty (message="E-Mail is required.") | ||||||
|  |    private String email; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import jakarta.validation.constraints.NotEmpty; | ||||||
|  | import lombok.*; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * EncryptCredentials | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @Setter | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | @ToString | ||||||
|  | public class EncryptCredentials { | ||||||
|  |    private long userId; | ||||||
|  |    private String email; | ||||||
|  |    @NotEmpty(message="encryption password id is required.") | ||||||
|  |    private String encryptPassword; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,30 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonRawValue; | ||||||
|  | import com.fasterxml.jackson.databind.JsonNode; | ||||||
|  | import jakarta.validation.constraints.NotEmpty; | ||||||
|  | import jakarta.validation.constraints.NotNull; | ||||||
|  | import jakarta.validation.constraints.Size; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import lombok.Getter; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  | import lombok.Setter; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * NewSecret | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @Setter | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class NewSecret { | ||||||
|  |    @NotEmpty (message="email is required.") | ||||||
|  |    private String email; | ||||||
|  | 
 | ||||||
|  |    @NotNull (message="secret is required.") | ||||||
|  |    private JsonNode content; | ||||||
|  | 
 | ||||||
|  |    @NotEmpty (message="encryption password id is required.") | ||||||
|  |    private String encryptPassword; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import jakarta.validation.constraints.NotEmpty; | ||||||
|  | import jakarta.validation.constraints.Pattern; | ||||||
|  | import jakarta.validation.constraints.Size; | ||||||
|  | import lombok.*; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * RegisterUser | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Value | ||||||
|  | public class RegisterUser { | ||||||
|  | 
 | ||||||
|  |    @NotEmpty(message="Firstname is required.") | ||||||
|  |    @Size(min=2, max=25, message="Firstname size has to be 2 up to 25 characters.") | ||||||
|  |    private String firstName; | ||||||
|  | 
 | ||||||
|  |    @NotEmpty (message="Lastname is required.") | ||||||
|  |    @Size(min=2, max=25, message="Lastname size has to be 2 up to 25 characters.") | ||||||
|  |    private String lastName; | ||||||
|  | 
 | ||||||
|  |    @NotEmpty (message="E-Mail is required.") | ||||||
|  |    private String email; | ||||||
|  | 
 | ||||||
|  |    @NotEmpty (message="Password is required.") | ||||||
|  |    private String password; | ||||||
|  | 
 | ||||||
|  |    @NotEmpty (message="Password-confirmation is required.") | ||||||
|  |    private String passwordConfirmation; | ||||||
|  | 
 | ||||||
|  |    private String recaptchaToken; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonRawValue; | ||||||
|  | import jakarta.persistence.*; | ||||||
|  | import lombok.*; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Secret | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @Setter | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | @ToString | ||||||
|  | @Entity | ||||||
|  | @Table(name = "secret") | ||||||
|  | public class Secret { | ||||||
|  |    @Id | ||||||
|  |    @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||||
|  |    private Long id; | ||||||
|  | 
 | ||||||
|  |    @Column(nullable = false, name="user_id") | ||||||
|  |    private Long userId; | ||||||
|  | 
 | ||||||
|  |    @Column(nullable = false, name="content") | ||||||
|  |    private String content; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.model; | ||||||
|  | 
 | ||||||
|  | import jakarta.persistence.*; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import lombok.Getter; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  | import lombok.Setter; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * User | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @Setter | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | @Entity | ||||||
|  | @Table(name = "user") | ||||||
|  | public class User { | ||||||
|  |    @Id | ||||||
|  |    @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||||
|  |    private Long id; | ||||||
|  | 
 | ||||||
|  |    @Column(nullable = false, name="first_name") | ||||||
|  |    private String firstName; | ||||||
|  | 
 | ||||||
|  |    @Column(nullable = false, name="last_name") | ||||||
|  |    private String lastName; | ||||||
|  | 
 | ||||||
|  |    @Column(nullable = false, unique = true) | ||||||
|  |    private String email; | ||||||
|  | 
 | ||||||
|  |    @Column(nullable = false) | ||||||
|  |    private String password; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.repository; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.Secret; | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository; | ||||||
|  | 
 | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SecretRepository | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | public interface SecretRepository extends JpaRepository<Secret, Long> { | ||||||
|  |    List<Secret> findByUserId(Long userId); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.repository; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.User; | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository; | ||||||
|  | 
 | ||||||
|  | import java.util.Optional; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * UserRepository | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | public interface UserRepository extends JpaRepository<User, Long> { | ||||||
|  |    Optional<User> findByEmail(String email); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.service; | ||||||
|  | 
 | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * PasswordEncryptionService | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | public class PasswordEncryptionService { | ||||||
|  |    //todo ergänzen! | ||||||
|  | 
 | ||||||
|  |    public PasswordEncryptionService() { | ||||||
|  |       //todo anpassen! | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    public String hashPassword(String password) { | ||||||
|  |       //todo anpassen! | ||||||
|  |       return password; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.service; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.Secret; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SecretService | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | public interface SecretService { | ||||||
|  |    Secret createSecret(Secret secret); | ||||||
|  | 
 | ||||||
|  |    Secret getSecretById(Long secretId); | ||||||
|  | 
 | ||||||
|  |    List<Secret> getAllSecrets(); | ||||||
|  | 
 | ||||||
|  |    Secret updateSecret(Secret secret); | ||||||
|  | 
 | ||||||
|  |    void deleteSecret(Long secretId); | ||||||
|  | 
 | ||||||
|  |    List<Secret> getSecretsByUserId(Long userId); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.service; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.User; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * UserService | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | public interface UserService { | ||||||
|  |    User createUser(User user); | ||||||
|  | 
 | ||||||
|  |    User getUserById(Long userId); | ||||||
|  | 
 | ||||||
|  |    User findByEmail(String email); | ||||||
|  | 
 | ||||||
|  |    List<User> getAllUsers(); | ||||||
|  | 
 | ||||||
|  |    User updateUser(User user); | ||||||
|  | 
 | ||||||
|  |    void deleteUser(Long userId); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.service.impl; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.Secret; | ||||||
|  | import ch.bbw.pr.tresorbackend.repository.SecretRepository; | ||||||
|  | import ch.bbw.pr.tresorbackend.service.SecretService; | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | 
 | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SecretServiceImpl | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class SecretServiceImpl implements SecretService { | ||||||
|  | 
 | ||||||
|  |    private SecretRepository secretRepository; | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public Secret createSecret(Secret card) { | ||||||
|  |       return secretRepository.save(card); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public Secret getSecretById(Long secretId) { | ||||||
|  |       Optional<Secret> optionalSecret = secretRepository.findById(secretId); | ||||||
|  |       return optionalSecret.get(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public List<Secret> getAllSecrets() { | ||||||
|  |       return (List<Secret>) secretRepository.findAll(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public Secret updateSecret(Secret secret) { | ||||||
|  |       Secret existingSecret = secretRepository.findById(secret.getId()).get(); | ||||||
|  |       existingSecret.setUserId(secret.getUserId()); | ||||||
|  |       existingSecret.setContent(secret.getContent()); | ||||||
|  |       Secret updatedSecret = secretRepository.save(existingSecret); | ||||||
|  |       return updatedSecret; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public void deleteSecret(Long secretId) { | ||||||
|  |       secretRepository.deleteById(secretId); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public List<Secret> getSecretsByUserId(Long userId) { | ||||||
|  |       return secretRepository.findByUserId(userId); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,58 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.service.impl; | ||||||
|  | 
 | ||||||
|  | import ch.bbw.pr.tresorbackend.model.User; | ||||||
|  | import ch.bbw.pr.tresorbackend.repository.UserRepository; | ||||||
|  | import ch.bbw.pr.tresorbackend.service.UserService; | ||||||
|  | 
 | ||||||
|  | import lombok.AllArgsConstructor; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * UserServiceImpl | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | @Service | ||||||
|  | @AllArgsConstructor | ||||||
|  | public class UserServiceImpl implements UserService { | ||||||
|  | 
 | ||||||
|  |    private UserRepository userRepository; | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public User createUser(User user) { | ||||||
|  |       return userRepository.save(user); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public User getUserById(Long userId) { | ||||||
|  |       Optional<User> optionalUser = userRepository.findById(userId); | ||||||
|  |       return optionalUser.get(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public User findByEmail(String email) { | ||||||
|  |       Optional<User> optionalUser = userRepository.findByEmail(email); | ||||||
|  |       return optionalUser.get(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public List<User> getAllUsers() { | ||||||
|  |       return (List<User>) userRepository.findAll(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public User updateUser(User user) { | ||||||
|  |       User existingUser = userRepository.findById(user.getId()).get(); | ||||||
|  |       existingUser.setFirstName(user.getFirstName()); | ||||||
|  |       existingUser.setLastName(user.getLastName()); | ||||||
|  |       existingUser.setEmail(user.getEmail()); | ||||||
|  |       User updatedUser = userRepository.save(existingUser); | ||||||
|  |       return updatedUser; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    @Override | ||||||
|  |    public void deleteUser(Long userId) { | ||||||
|  |       userRepository.deleteById(userId); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend.util; | ||||||
|  | 
 | ||||||
|  | import org.jasypt.util.text.AES256TextEncryptor; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * EncryptUtil | ||||||
|  |  * Used to encrypt content. | ||||||
|  |  * Not implemented yet. | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | public class EncryptUtil { | ||||||
|  | 
 | ||||||
|  |    //todo ergänzen! | ||||||
|  | 
 | ||||||
|  |    public EncryptUtil(String secretKey) { | ||||||
|  |       //todo ergänzen! | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    public String encrypt(String data) { | ||||||
|  |       //todo anpassen! | ||||||
|  |       return data; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    public String decrypt(String data) { | ||||||
|  |       //todo anpassen! | ||||||
|  |       return data; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | spring.application.name=tresorbackend | ||||||
|  | 
 | ||||||
|  | #variables can be overwritten by environment variables | ||||||
|  | spring.datasource.url=jdbc:mysql://localhost:3306/tresordb | ||||||
|  | spring.datasource.username=root | ||||||
|  | spring.datasource.password=1234 | ||||||
|  | 
 | ||||||
|  | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect | ||||||
|  | spring.jpa.hibernate.ddl-auto=update | ||||||
|  | 
 | ||||||
|  | CROSS_ORIGIN=http://localhost:3000 | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | package ch.bbw.pr.tresorbackend; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.springframework.boot.test.context.SpringBootTest; | ||||||
|  | 
 | ||||||
|  | @SpringBootTest | ||||||
|  | class TresorbackendApplicationTests { | ||||||
|  | 
 | ||||||
|  |    @Test | ||||||
|  |    void contextLoads() { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,51 @@ | ||||||
|  | -- | ||||||
|  | -- Datenbank: `tresordb` | ||||||
|  | -- | ||||||
|  | 
 | ||||||
|  | DROP DATABASE IF EXISTS tresordb; | ||||||
|  | CREATE DATABASE tresordb; | ||||||
|  | USE tresordb; | ||||||
|  | 
 | ||||||
|  | -- -------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- table user | ||||||
|  | -- | ||||||
|  | 
 | ||||||
|  | CREATE TABLE user ( | ||||||
|  |     id int NOT NULL AUTO_INCREMENT, | ||||||
|  |     first_name varchar(30) NOT NULL, | ||||||
|  |     last_name varchar(30) NOT NULL, | ||||||
|  |     email varchar(30) NOT NULL, | ||||||
|  |     password longtext NOT NULL, | ||||||
|  |     PRIMARY KEY (id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- table user content | ||||||
|  | -- | ||||||
|  | 
 | ||||||
|  | INSERT INTO `user` (`id`, `first_name`, `last_name`, `email`, `password`) VALUES | ||||||
|  | (1, 'Hans', 'Muster', 'hans.muster@bbw.ch', 'abcd'), | ||||||
|  | (2, 'Paula', 'Kuster', 'paula.kuster@bbw.ch', 'efgh'), | ||||||
|  | (3, 'Andrea', 'Oester', 'andrea.oester@bbw.ch', 'ijkl'); | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- table secret | ||||||
|  | -- | ||||||
|  | 
 | ||||||
|  | CREATE TABLE secret ( | ||||||
|  |     id int NOT NULL AUTO_INCREMENT, | ||||||
|  |     user_id int NOT NULL, | ||||||
|  |     content json NOT NULL, | ||||||
|  |     PRIMARY KEY (id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- table secret content | ||||||
|  | -- | ||||||
|  | 
 | ||||||
|  | INSERT INTO `secret` (`id`, `user_id`, `content`) VALUES | ||||||
|  |     (1, 1, '{"kindid":1,"kind":"credential","userName":"muster","password":"1234","url":"www.bbw.ch"}'), | ||||||
|  |     (2, 1, '{"kindid":2,"kind":"creditcard","cardtype":"Visa","cardnumber":"4242 4242 4242 4241","expiration":"12/27","cvv":"789"}'), | ||||||
|  |     (3, 1, '{"kindid":3,"kind":"note","title":"Eragon","content":"Und Eragon ging auf den Drachen zu."}'); | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | host="$1" | ||||||
|  | shift | ||||||
|  | cmd="$@" | ||||||
|  | 
 | ||||||
|  | echo "Waiting for $host to be ready..." | ||||||
|  | 
 | ||||||
|  | until mysqladmin ping -h "$host" --silent; do | ||||||
|  |   echo "MariaDB is unavailable - waiting..." | ||||||
|  |   sleep 2 | ||||||
|  | done | ||||||
|  | 
 | ||||||
|  | echo "MariaDB is up - executing command" | ||||||
|  | exec $cmd | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | #connection to backend api | ||||||
|  | # example http://localhost:8080/api | ||||||
|  | # port is optional | ||||||
|  | REACT_APP_API_PROTOCOL=http | ||||||
|  | REACT_APP_API_HOST=localhost | ||||||
|  | REACT_APP_API_PORT=8080 | ||||||
|  | REACT_APP_API_PATH=/api | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||||||
|  | 
 | ||||||
|  | # Node.js Modules | ||||||
|  | node_modules/ | ||||||
|  | package-lock.json | ||||||
|  | yarn.lock | ||||||
|  | 
 | ||||||
|  | # Build Artifacts | ||||||
|  | build/ | ||||||
|  | dist/ | ||||||
|  | 
 | ||||||
|  | # IntelliJ IDEA Files | ||||||
|  | .idea/ | ||||||
|  | *.iml | ||||||
|  | *.ipr | ||||||
|  | *.iws | ||||||
|  | 
 | ||||||
|  | # Logs | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|  | pnpm-debug.log* | ||||||
|  | *.log | ||||||
|  | 
 | ||||||
|  | # testing | ||||||
|  | coverage/ | ||||||
|  | 
 | ||||||
|  | # misc | ||||||
|  | .DS_Store | ||||||
|  | .env.local | ||||||
|  | .env.development.local | ||||||
|  | .env.test.local | ||||||
|  | .env.production.local | ||||||
|  | 
 | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|  | 
 | ||||||
|  | # Editor Settings | ||||||
|  | .vscode/ | ||||||
|  | *.swp | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | # Step one | ||||||
|  | 
 | ||||||
|  | # Build Stage: Node.js wird verwendet, um die App zu bauen | ||||||
|  | FROM node:14 AS build | ||||||
|  | 
 | ||||||
|  | # Set working directory | ||||||
|  | WORKDIR /App | ||||||
|  | 
 | ||||||
|  | # copy package.json and package-lock.json | ||||||
|  | COPY package*.json /App/ | ||||||
|  | 
 | ||||||
|  | # install Node.js-dependencies | ||||||
|  | RUN npm install | ||||||
|  | 
 | ||||||
|  | # copy application files | ||||||
|  | COPY . /App/ | ||||||
|  | 
 | ||||||
|  | # build the app | ||||||
|  | RUN npm run build | ||||||
|  | 
 | ||||||
|  | # Step two | ||||||
|  | 
 | ||||||
|  | # Serve Stage: use Nginx | ||||||
|  | FROM nginx:latest | ||||||
|  | 
 | ||||||
|  | # Copy the build files from the build stage to Nginx's html directory | ||||||
|  | COPY --from=build /App/build /usr/share/nginx/html | ||||||
|  | 
 | ||||||
|  | # copy nginx config to get a perfomanter app | ||||||
|  | COPY nginx.conf /etc/nginx/conf.d/default.conf | ||||||
|  | 
 | ||||||
|  | # Expose the default Nginx port | ||||||
|  | EXPOSE 80 | ||||||
|  | 
 | ||||||
|  | # Start Nginx | ||||||
|  | CMD ["nginx", "-g", "daemon off;"] | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | # The secret tresor application frontend | ||||||
|  | 
 | ||||||
|  | ## Technik | ||||||
|  | 
 | ||||||
|  | - basiert auf React | ||||||
|  | 
 | ||||||
|  | ## Umgebungsvariablen | ||||||
|  | 
 | ||||||
|  | - In der **Datei .env** sind die von der Applikation **verwendeten Umgebungsvariablen** deklariert. | ||||||
|  | - Falls die Umgebungsvariablen _aussen_ nicht gesetzt sind, wird ein Default-Wert verwendet. | ||||||
|  | 
 | ||||||
|  | ## Hinweise zur Anwendung der Applikation | ||||||
|  | 
 | ||||||
|  | - Die Applikation ist für den Unterricht gedacht. | ||||||
|  | - Die vorliegende Version hat keine Security implementiert. | ||||||
|  | - So spielt das Passwort (noch) keine Rolle. | ||||||
|  | 
 | ||||||
|  | - Wenn die Applikation gestartet ist, kann man mit</br> | ||||||
|  |   _Admin->All user_</br> | ||||||
|  |   Vom Backend das Verzeichnis aller User abrufen. | ||||||
|  | 
 | ||||||
|  | - Mit</br> | ||||||
|  |   _User->Register_</br> | ||||||
|  |   Kein ein neuer User via Backend gespeichert werden | ||||||
|  | - Danach ist ein Login des Users notwendig.</br> | ||||||
|  |   _User->Login_ mit Email und Passwort</br> | ||||||
|  |   Diese Login-Daten sind für die _Secrets_ Funktionen notwendig. | ||||||
|  | - Es können drei unterschiedliche Secrets gespeicher werden. | ||||||
|  | - Und es können die eigenen Secrets abgerufen werden. | ||||||
|  | 
 | ||||||
|  | ## Build image | ||||||
|  | 
 | ||||||
|  | see Dockerfile | ||||||
|  | 
 | ||||||
|  | ```Bash | ||||||
|  | docker build -t tresorfrontendimg . | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Start container local | ||||||
|  | 
 | ||||||
|  | ```Bash | ||||||
|  | docker run -p 80:80 --name tresorfrontend tresorfrontendimg | ||||||
|  | 
 | ||||||
|  | (c) 2024 P. Rutschmann | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | server { | ||||||
|  |     listen 80; | ||||||
|  |     server_name localhost; | ||||||
|  | 
 | ||||||
|  |     location / { | ||||||
|  |         root /usr/share/nginx/html; | ||||||
|  |         index index.html; | ||||||
|  |         try_files $uri /index.html; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | { | ||||||
|  |   "name": "01_tresorfrontend", | ||||||
|  |   "version": "0.1.0", | ||||||
|  |   "private": true, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@testing-library/jest-dom": "^5.17.0", | ||||||
|  |     "@testing-library/react": "^13.4.0", | ||||||
|  |     "@testing-library/user-event": "^13.5.0", | ||||||
|  |     "axios": "^1.6.8", | ||||||
|  |     "react": "^18.2.0", | ||||||
|  |     "react-dom": "^18.2.0", | ||||||
|  |     "react-google-recaptcha": "^3.1.0", | ||||||
|  |     "react-router-dom": "^6.22.3", | ||||||
|  |     "react-scripts": "5.0.1", | ||||||
|  |     "reactstrap": "^9.2.2", | ||||||
|  |     "web-vitals": "^2.1.4" | ||||||
|  |   }, | ||||||
|  |   "scripts": { | ||||||
|  |     "start": "react-scripts start", | ||||||
|  |     "build": "react-scripts build", | ||||||
|  |     "test": "react-scripts test", | ||||||
|  |     "eject": "react-scripts eject" | ||||||
|  |   }, | ||||||
|  |   "eslintConfig": { | ||||||
|  |     "extends": [ | ||||||
|  |       "react-app", | ||||||
|  |       "react-app/jest" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "browserslist": { | ||||||
|  |     "production": [ | ||||||
|  |       ">0.2%", | ||||||
|  |       "not dead", | ||||||
|  |       "not op_mini all" | ||||||
|  |     ], | ||||||
|  |     "development": [ | ||||||
|  |       "last 1 chrome version", | ||||||
|  |       "last 1 firefox version", | ||||||
|  |       "last 1 safari version" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 KiB | 
|  | @ -0,0 +1,43 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8" /> | ||||||
|  |     <link rel="icon" href="./favicon.ico" /> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||||
|  |     <meta name="theme-color" content="#000000" /> | ||||||
|  |     <meta | ||||||
|  |       name="description" | ||||||
|  |       content="Web site created using create-react-app" | ||||||
|  |     /> | ||||||
|  |     <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | ||||||
|  |     <!-- | ||||||
|  |       manifest.json provides metadata used when your web app is installed on a | ||||||
|  |       user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ | ||||||
|  |     --> | ||||||
|  |     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> | ||||||
|  |     <!-- | ||||||
|  |       Notice the use of %PUBLIC_URL% in the tags above. | ||||||
|  |       It will be replaced with the URL of the `public` folder during the build. | ||||||
|  |       Only files inside the `public` folder can be referenced from the HTML. | ||||||
|  | 
 | ||||||
|  |       Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | ||||||
|  |       work correctly both with client-side routing and a non-root public URL. | ||||||
|  |       Learn how to configure a non-root public URL by running `npm run build`. | ||||||
|  |     --> | ||||||
|  |     <title>React App</title> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||||
|  |     <div id="root"></div> | ||||||
|  |     <!-- | ||||||
|  |       This HTML file is a template. | ||||||
|  |       If you open it directly in the browser, you will see an empty page. | ||||||
|  | 
 | ||||||
|  |       You can add webfonts, meta tags, or analytics to this file. | ||||||
|  |       The build step will place the bundled scripts into the <body> tag. | ||||||
|  | 
 | ||||||
|  |       To begin the development, run `npm start` or `yarn start`. | ||||||
|  |       To create a production bundle, use `npm run build` or `yarn build`. | ||||||
|  |     --> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | { | ||||||
|  |   "short_name": "React App", | ||||||
|  |   "name": "Create React App Sample", | ||||||
|  |   "icons": [ | ||||||
|  |     { | ||||||
|  |       "src": "favicon.ico", | ||||||
|  |       "sizes": "64x64 32x32 24x24 16x16", | ||||||
|  |       "type": "image/x-icon" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "start_url": ".", | ||||||
|  |   "display": "standalone", | ||||||
|  |   "theme_color": "#000000", | ||||||
|  |   "background_color": "#ffffff" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | # https://www.robotstxt.org/robotstxt.html | ||||||
|  | User-agent: * | ||||||
|  | Disallow: | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | .App { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .App-logo { | ||||||
|  |   height: 40vmin; | ||||||
|  |   pointer-events: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (prefers-reduced-motion: no-preference) { | ||||||
|  |   .App-logo { | ||||||
|  |     animation: App-logo-spin infinite 20s linear; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .App-header { | ||||||
|  |   background-color: #282c34; | ||||||
|  |   min-height: 100vh; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   font-size: calc(10px + 2vmin); | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .App-link { | ||||||
|  |   color: #61dafb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes App-logo-spin { | ||||||
|  |   from { | ||||||
|  |     transform: rotate(0deg); | ||||||
|  |   } | ||||||
|  |   to { | ||||||
|  |     transform: rotate(360deg); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | import React, {useState} from 'react'; | ||||||
|  | import {BrowserRouter, Route, Routes} from "react-router-dom"; | ||||||
|  | import './App.css'; | ||||||
|  | import './css/mvp.css'; | ||||||
|  | import Home from './pages/Home'; | ||||||
|  | import Layout from "./pages/Layout"; | ||||||
|  | import NoPage from "./pages/NoPage"; | ||||||
|  | import Users from './pages/user/Users'; | ||||||
|  | import LoginUser from "./pages/user/LoginUser"; | ||||||
|  | import RegisterUser from "./pages/user/RegisterUser"; | ||||||
|  | import Secrets from "./pages/secret/Secrets"; | ||||||
|  | import NewCredential from "./pages/secret/NewCredential"; | ||||||
|  | import NewCreditCard from "./pages/secret/NewCreditCard"; | ||||||
|  | import NewNote from "./pages/secret/NewNote"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * App | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | function App() { | ||||||
|  |     const [loginValues, setLoginValues] = useState({ | ||||||
|  |         email: "", | ||||||
|  |         password: "", | ||||||
|  |     }); | ||||||
|  |     return ( | ||||||
|  |         <BrowserRouter> | ||||||
|  |             <Routes> | ||||||
|  |                 <Route path="/" element={<Layout loginValues={loginValues}/>}> | ||||||
|  |                     <Route index element={<Home/>}/>}/> | ||||||
|  |                     <Route path="/user/users" element={<Users loginValues={loginValues}/>}/> | ||||||
|  |                     <Route path="/user/login" element={<LoginUser loginValues={loginValues} setLoginValues={setLoginValues}/>}/> | ||||||
|  |                     <Route path="/user/register" element={<RegisterUser loginValues={loginValues} setLoginValues={setLoginValues}/>}/> | ||||||
|  |                     <Route path="/secret/secrets" element={<Secrets loginValues={loginValues}/>}/> | ||||||
|  |                     <Route path="/secret/newcredential" element={<NewCredential loginValues={loginValues}/>}/> | ||||||
|  |                     <Route path="/secret/newcreditcard" element={<NewCreditCard loginValues={loginValues}/>}/> | ||||||
|  |                     <Route path="/secret/newnote" element={<NewNote loginValues={loginValues}/>}/> | ||||||
|  |                     <Route path="*" element={<NoPage/>}/> | ||||||
|  |                 </Route> | ||||||
|  |             </Routes> | ||||||
|  |         </BrowserRouter> | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default App; | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | import { render, screen } from '@testing-library/react'; | ||||||
|  | import App from './App'; | ||||||
|  | 
 | ||||||
|  | test('renders learn react link', () => { | ||||||
|  |   render(<App />); | ||||||
|  |   const linkElement = screen.getByText(/learn react/i); | ||||||
|  |   expect(linkElement).toBeInTheDocument(); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,75 @@ | ||||||
|  | /** | ||||||
|  |  * Fetch methodes for secret api calls | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | //Post secret to server
 | ||||||
|  | export const postSecret = async ({loginValues, content}) => { | ||||||
|  |     const protocol = process.env.REACT_APP_API_PROTOCOL; // "http"
 | ||||||
|  |     const host = process.env.REACT_APP_API_HOST; // "localhost"
 | ||||||
|  |     const port = process.env.REACT_APP_API_PORT; // "8080"
 | ||||||
|  |     const path = process.env.REACT_APP_API_PATH; // "/api"
 | ||||||
|  |     const portPart = port ? `:${port}` : ''; // port is optional
 | ||||||
|  |     const API_URL = `${protocol}://${host}${portPart}${path}`; | ||||||
|  |     console.log(loginValues) | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         const response = await fetch(`${API_URL}/secrets`, { | ||||||
|  |             method: 'POST', | ||||||
|  |             headers: { | ||||||
|  |                 'Content-Type': 'application/json' | ||||||
|  |             }, | ||||||
|  |             body: JSON.stringify({ | ||||||
|  |                 email: loginValues.email, | ||||||
|  |                 encryptPassword: loginValues.password, | ||||||
|  |                 content: content | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (!response.ok) { | ||||||
|  |             const errorData = await response.json(); | ||||||
|  |             throw new Error(errorData.message || 'Server response failed.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const data = await response.json(); | ||||||
|  |         console.log('Secret successfully posted:', data); | ||||||
|  |         return data; | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Error posting secret:', error.message); | ||||||
|  |         throw new Error('Failed to save secret. ' || error.message); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //get all secrets for a user identified by its email
 | ||||||
|  | export const getSecretsforUser = async (loginValues) => { | ||||||
|  |     const protocol = process.env.REACT_APP_API_PROTOCOL; // "http"
 | ||||||
|  |     const host = process.env.REACT_APP_API_HOST; // "localhost"
 | ||||||
|  |     const port = process.env.REACT_APP_API_PORT; // "8080"
 | ||||||
|  |     const path = process.env.REACT_APP_API_PATH; // "/api"
 | ||||||
|  |     const portPart = port ? `:${port}` : ''; // port is optional
 | ||||||
|  |     const API_URL = `${protocol}://${host}${portPart}${path}`; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         const response = await fetch(`${API_URL}/secrets/byemail`, { | ||||||
|  |             method: 'POST', | ||||||
|  |             headers: { | ||||||
|  |                 'Content-Type': 'application/json' | ||||||
|  |             }, | ||||||
|  |             body: JSON.stringify({ | ||||||
|  |                 email: loginValues.email, | ||||||
|  |                 encryptPassword: loginValues.password | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (!response.ok) { | ||||||
|  |             const errorData = await response.json(); | ||||||
|  |             throw new Error(errorData.message || 'Server response failed.'); | ||||||
|  |         } | ||||||
|  |         const data = await response.json(); | ||||||
|  |         console.log('Secret successfully got:', data); | ||||||
|  |         return data; | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Failed to get secrets:', error.message); | ||||||
|  |         throw new Error('Failed to get secrets. ' || error.message); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,71 @@ | ||||||
|  | /** | ||||||
|  |  * Fetch methodes for user api calls | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export const getUsers = async () => { | ||||||
|  |     const protocol = process.env.REACT_APP_API_PROTOCOL; // "http"
 | ||||||
|  |     const host = process.env.REACT_APP_API_HOST; // "localhost"
 | ||||||
|  |     const port = process.env.REACT_APP_API_PORT; // "8080"
 | ||||||
|  |     const path = process.env.REACT_APP_API_PATH; // "/api"
 | ||||||
|  |     const portPart = port ? `:${port}` : ''; // port is optional
 | ||||||
|  |     const API_URL = `${protocol}://${host}${portPart}${path}`; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         const response = await fetch(`${API_URL}/users`, { | ||||||
|  |             method: 'Get', | ||||||
|  |             headers: { | ||||||
|  |                 'Accept': 'application/json' | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (!response.ok) { | ||||||
|  |             const errorData = await response.json(); | ||||||
|  |             throw new Error(errorData.message || 'Server response failed.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const data = await response.json(); | ||||||
|  |         console.log('User successfully got:', data); | ||||||
|  |         return data; | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Failed to get user:', error.message); | ||||||
|  |         throw new Error('Failed to get user. ' || error.message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const postUser = async (content) => { | ||||||
|  |     const protocol = process.env.REACT_APP_API_PROTOCOL; // "http"
 | ||||||
|  |     const host = process.env.REACT_APP_API_HOST; // "localhost"
 | ||||||
|  |     const port = process.env.REACT_APP_API_PORT; // "8080"
 | ||||||
|  |     const path = process.env.REACT_APP_API_PATH; // "/api"
 | ||||||
|  |     const portPart = port ? `:${port}` : ''; // port is optional
 | ||||||
|  |     const API_URL = `${protocol}://${host}${portPart}${path}`; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         const response = await fetch(`${API_URL}/users`, { | ||||||
|  |             method: 'POST', | ||||||
|  |             headers: { | ||||||
|  |                 'Content-Type': 'application/json' | ||||||
|  |             }, | ||||||
|  |             body: JSON.stringify({ | ||||||
|  |                 firstName: `${content.firstName}`, | ||||||
|  |                 lastName: `${content.lastName}`, | ||||||
|  |                 email: `${content.email}`, | ||||||
|  |                 password: `${content.password}`, | ||||||
|  |                 passwordConfirmation: `${content.passwordConfirmation}` | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (!response.ok) { | ||||||
|  |             const errorData = await response.json(); | ||||||
|  |             throw new Error(errorData.message || 'Server response failed.'); | ||||||
|  |         } | ||||||
|  |         const data = await response.json(); | ||||||
|  |         console.log('User successfully posted:', data); | ||||||
|  |         return data; | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Failed to post user:', error.message); | ||||||
|  |         throw new Error('Failed to save user. ' || error.message); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,470 @@ | ||||||
|  | /* MVP.css v1.7.4 - https://github.com/andybrewer/mvp */ | ||||||
|  | 
 | ||||||
|  | :root { | ||||||
|  |     --border-radius: 5px; | ||||||
|  |     --box-shadow: 2px 2px 10px; | ||||||
|  |     --color: #118bee; | ||||||
|  |     --color-accent: #118bee15; | ||||||
|  |     --color-bg: #fff; | ||||||
|  |     --color-bg-secondary: #e9e9e9; | ||||||
|  |     --color-secondary: #920de9; | ||||||
|  |     --color-secondary-accent: #920de90b; | ||||||
|  |     --color-shadow: #f4f4f4; | ||||||
|  |     --color-text: #000; | ||||||
|  |     --color-text-secondary: #999; | ||||||
|  |     --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; | ||||||
|  |     --hover-brightness: 1.2; | ||||||
|  |     --justify-important: center; | ||||||
|  |     --justify-normal: left; | ||||||
|  |     --line-height: 1.5; | ||||||
|  |     --width-card: 285px; | ||||||
|  |     --width-card-medium: 460px; | ||||||
|  |     --width-card-wide: 800px; | ||||||
|  |     --width-content: 1080px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | @media (prefers-color-scheme: dark) { | ||||||
|  |     :root { | ||||||
|  |         --color: #0097fc; | ||||||
|  |         --color-accent: #0097fc4f; | ||||||
|  |         --color-bg: #333; | ||||||
|  |         --color-bg-secondary: #555; | ||||||
|  |         --color-secondary: #e20de9; | ||||||
|  |         --color-secondary-accent: #e20de94f; | ||||||
|  |         --color-shadow: #bbbbbb20; | ||||||
|  |         --color-text: #f7f7f7; | ||||||
|  |         --color-text-secondary: #aaa; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | /* Layout */ | ||||||
|  | article aside { | ||||||
|  |     background: var(--color-secondary-accent); | ||||||
|  |     border-left: 4px solid var(--color-secondary); | ||||||
|  |     padding: 0.01rem 0.8rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body { | ||||||
|  |     background: var(--color-bg); | ||||||
|  |     color: var(--color-text); | ||||||
|  |     font-family: var(--font-family); | ||||||
|  |     line-height: var(--line-height); | ||||||
|  |     margin: 0; | ||||||
|  |     overflow-x: hidden; | ||||||
|  |     padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer, | ||||||
|  | header, | ||||||
|  | main { | ||||||
|  |     margin: 0 auto; | ||||||
|  |     max-width: var(--width-content); | ||||||
|  |     padding: 3rem 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | hr { | ||||||
|  |     background-color: var(--color-bg-secondary); | ||||||
|  |     border: none; | ||||||
|  |     height: 1px; | ||||||
|  |     margin: 4rem 0; | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section { | ||||||
|  |     display: flex; | ||||||
|  |     flex-wrap: wrap; | ||||||
|  |     justify-content: var(--justify-important); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section img, | ||||||
|  | article img { | ||||||
|  |     max-width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section pre { | ||||||
|  |     overflow: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section aside { | ||||||
|  |     border: 1px solid var(--color-bg-secondary); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     box-shadow: var(--box-shadow) var(--color-shadow); | ||||||
|  |     margin: 1rem; | ||||||
|  |     padding: 1.25rem; | ||||||
|  |     width: var(--width-card); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section aside:hover { | ||||||
|  |     box-shadow: var(--box-shadow) var(--color-bg-secondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | [hidden] { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Headers */ | ||||||
|  | article header, | ||||||
|  | div header, | ||||||
|  | main header { | ||||||
|  |     padding-top: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header { | ||||||
|  |     text-align: var(--justify-important); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header a b, | ||||||
|  | header a em, | ||||||
|  | header a i, | ||||||
|  | header a strong { | ||||||
|  |     margin-left: 0.5rem; | ||||||
|  |     margin-right: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | header nav img { | ||||||
|  |     margin: 1rem 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | section header { | ||||||
|  |     padding-top: 0; | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Nav */ | ||||||
|  | nav { | ||||||
|  |     align-items: center; | ||||||
|  |     display: flex; | ||||||
|  |     font-weight: bold; | ||||||
|  |     justify-content: space-between; | ||||||
|  |     /*Peru margin-bottom: 7rem; */ | ||||||
|  |     margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nav ul { | ||||||
|  |     list-style: none; | ||||||
|  |     padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nav ul li { | ||||||
|  |     display: inline-block; | ||||||
|  |     margin: 0 0.5rem; | ||||||
|  |     position: relative; | ||||||
|  |     text-align: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Nav Dropdown */ | ||||||
|  | nav ul li:hover ul { | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nav ul li ul { | ||||||
|  |     background: var(--color-bg); | ||||||
|  |     border: 1px solid var(--color-bg-secondary); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     box-shadow: var(--box-shadow) var(--color-shadow); | ||||||
|  |     display: none; | ||||||
|  |     height: auto; | ||||||
|  |     left: -2px; | ||||||
|  |     padding: .5rem 1rem; | ||||||
|  |     position: absolute; | ||||||
|  |     top: 1.7rem; | ||||||
|  |     white-space: nowrap; | ||||||
|  |     width: auto; | ||||||
|  |     z-index: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nav ul li ul::before { | ||||||
|  |     /* fill gap above to make mousing over them easier */ | ||||||
|  |     content: ""; | ||||||
|  |     position: absolute; | ||||||
|  |     left: 0; | ||||||
|  |     right: 0; | ||||||
|  |     top: -0.5rem; | ||||||
|  |     height: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nav ul li ul li, | ||||||
|  | nav ul li ul li a { | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Typography */ | ||||||
|  | code, | ||||||
|  | samp { | ||||||
|  |     background-color: var(--color-accent); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     color: var(--color-text); | ||||||
|  |     display: inline-block; | ||||||
|  |     margin: 0 0.1rem; | ||||||
|  |     padding: 0 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | details { | ||||||
|  |     margin: 1.3rem 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | details summary { | ||||||
|  |     font-weight: bold; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, | ||||||
|  | h2, | ||||||
|  | h3, | ||||||
|  | h4, | ||||||
|  | h5, | ||||||
|  | h6 { | ||||||
|  |     line-height: var(--line-height); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | mark { | ||||||
|  |     padding: 0.1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ol li, | ||||||
|  | ul li { | ||||||
|  |     padding: 0.2rem 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | p { | ||||||
|  |     margin: 0.75rem 0; | ||||||
|  |     padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pre { | ||||||
|  |     margin: 1rem 0; | ||||||
|  |     max-width: var(--width-card-wide); | ||||||
|  |     padding: 1rem 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pre code, | ||||||
|  | pre samp { | ||||||
|  |     display: block; | ||||||
|  |     max-width: var(--width-card-wide); | ||||||
|  |     padding: 0.5rem 2rem; | ||||||
|  |     white-space: pre-wrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | small { | ||||||
|  |     color: var(--color-text-secondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sup { | ||||||
|  |     background-color: var(--color-secondary); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     color: var(--color-bg); | ||||||
|  |     font-size: xx-small; | ||||||
|  |     font-weight: bold; | ||||||
|  |     margin: 0.2rem; | ||||||
|  |     padding: 0.2rem 0.3rem; | ||||||
|  |     position: relative; | ||||||
|  |     top: -2px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Links */ | ||||||
|  | a { | ||||||
|  |     color: var(--color); | ||||||
|  |     display: inline-block; | ||||||
|  |     font-weight: bold; | ||||||
|  |     text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a:hover { | ||||||
|  |     filter: brightness(var(--hover-brightness)); | ||||||
|  |     text-decoration: underline; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a b, | ||||||
|  | a em, | ||||||
|  | a i, | ||||||
|  | a strong, | ||||||
|  | button { | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     display: inline-block; | ||||||
|  |     font-size: medium; | ||||||
|  |     font-weight: bold; | ||||||
|  |     line-height: var(--line-height); | ||||||
|  |     margin: 0.5rem 0; | ||||||
|  |     padding: 1rem 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button { | ||||||
|  |     font-family: var(--font-family); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:hover { | ||||||
|  |     cursor: pointer; | ||||||
|  |     filter: brightness(var(--hover-brightness)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a b, | ||||||
|  | a strong, | ||||||
|  | button { | ||||||
|  |     background-color: var(--color); | ||||||
|  |     border: 2px solid var(--color); | ||||||
|  |     color: var(--color-bg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a em, | ||||||
|  | a i { | ||||||
|  |     border: 2px solid var(--color); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     color: var(--color); | ||||||
|  |     display: inline-block; | ||||||
|  |     padding: 1rem 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | article aside a { | ||||||
|  |     color: var(--color-secondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Images */ | ||||||
|  | figure { | ||||||
|  |     margin: 0; | ||||||
|  |     padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | figure img { | ||||||
|  |     max-width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | figure figcaption { | ||||||
|  |     color: var(--color-text-secondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Forms */ | ||||||
|  | 
 | ||||||
|  | button:disabled, | ||||||
|  | input:disabled { | ||||||
|  |     background: var(--color-bg-secondary); | ||||||
|  |     border-color: var(--color-bg-secondary); | ||||||
|  |     color: var(--color-text-secondary); | ||||||
|  |     cursor: not-allowed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button[disabled]:hover { | ||||||
|  |     filter: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | form { | ||||||
|  |     border: 1px solid var(--color-bg-secondary); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     box-shadow: var(--box-shadow) var(--color-shadow); | ||||||
|  |     display: block; | ||||||
|  |     max-width: var(--width-card-wide); | ||||||
|  |     min-width: var(--width-card); | ||||||
|  |     padding: 1.5rem; | ||||||
|  |     text-align: var(--justify-normal); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | form header { | ||||||
|  |     margin: 1.5rem 0; | ||||||
|  |     padding: 1.5rem 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input, | ||||||
|  | label, | ||||||
|  | select, | ||||||
|  | textarea { | ||||||
|  |     display: block; | ||||||
|  |     font-size: inherit; | ||||||
|  |     max-width: var(--width-card-wide); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="checkbox"], | ||||||
|  | input[type="radio"] { | ||||||
|  |     display: inline-block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="checkbox"]+label, | ||||||
|  | input[type="radio"]+label { | ||||||
|  |     display: inline-block; | ||||||
|  |     font-weight: normal; | ||||||
|  |     position: relative; | ||||||
|  |     top: 1px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input, | ||||||
|  | select, | ||||||
|  | textarea { | ||||||
|  |     border: 1px solid var(--color-bg-secondary); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     margin-bottom: 1rem; | ||||||
|  |     padding: 0.4rem 0.8rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[readonly], | ||||||
|  | textarea[readonly] { | ||||||
|  |     background-color: var(--color-bg-secondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label { | ||||||
|  |     font-weight: bold; | ||||||
|  |     margin-bottom: 0.2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Tables */ | ||||||
|  | table { | ||||||
|  |     border: 1px solid var(--color-bg-secondary); | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     border-spacing: 0; | ||||||
|  |     display: inline-block; | ||||||
|  |     max-width: 100%; | ||||||
|  |     overflow-x: auto; | ||||||
|  |     padding: 0; | ||||||
|  |     white-space: nowrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table td, | ||||||
|  | table th, | ||||||
|  | table tr { | ||||||
|  |     padding: 0.4rem 0.8rem; | ||||||
|  |     text-align: var(--justify-important); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table thead { | ||||||
|  |     background-color: var(--color); | ||||||
|  |     border-collapse: collapse; | ||||||
|  |     border-radius: var(--border-radius); | ||||||
|  |     color: var(--color-bg); | ||||||
|  |     margin: 0; | ||||||
|  |     padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table thead th:first-child { | ||||||
|  |     border-top-left-radius: var(--border-radius); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table thead th:last-child { | ||||||
|  |     border-top-right-radius: var(--border-radius); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table thead th:first-child, | ||||||
|  | table tr td:first-child { | ||||||
|  |     text-align: var(--justify-normal); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | table tr:nth-child(even) { | ||||||
|  |     background-color: var(--color-accent); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Quotes */ | ||||||
|  | blockquote { | ||||||
|  |     display: block; | ||||||
|  |     font-size: x-large; | ||||||
|  |     line-height: var(--line-height); | ||||||
|  |     margin: 1rem auto; | ||||||
|  |     max-width: var(--width-card-medium); | ||||||
|  |     padding: 1.5rem 1rem; | ||||||
|  |     text-align: var(--justify-important); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | blockquote footer { | ||||||
|  |     color: var(--color-text-secondary); | ||||||
|  |     display: block; | ||||||
|  |     font-size: small; | ||||||
|  |     line-height: var(--line-height); | ||||||
|  |     padding: 1.5rem 0; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | body { | ||||||
|  |   margin: 0; | ||||||
|  |   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | ||||||
|  |     'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | ||||||
|  |     sans-serif; | ||||||
|  |   -webkit-font-smoothing: antialiased; | ||||||
|  |   -moz-osx-font-smoothing: grayscale; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | code { | ||||||
|  |   font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', | ||||||
|  |     monospace; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | import React from 'react'; | ||||||
|  | import ReactDOM from 'react-dom/client'; | ||||||
|  | import './index.css'; | ||||||
|  | import App from './App'; | ||||||
|  | import reportWebVitals from './reportWebVitals'; | ||||||
|  | 
 | ||||||
|  | const root = ReactDOM.createRoot(document.getElementById('root')); | ||||||
|  | root.render( | ||||||
|  |   <React.StrictMode> | ||||||
|  |     <App /> | ||||||
|  |   </React.StrictMode> | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | // If you want to start measuring performance in your app, pass a function
 | ||||||
|  | // to log results (for example: reportWebVitals(console.log))
 | ||||||
|  | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
 | ||||||
|  | reportWebVitals(); | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | import '../App.css'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Home | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | const Home = () => { | ||||||
|  |     return ( | ||||||
|  |         <> | ||||||
|  |             <h1>Speichern Sie Ihre Daten sicher ab.</h1> | ||||||
|  |             <form> | ||||||
|  |                 <p>In dieser Applikation können Sie, nachdem Sie sich registriert haben, Ihre sensitiven Daten verschlüsselt | ||||||
|  |                     in einer Datenbank speichern.</p> | ||||||
|  |                 <p>Erstellen Sie ein neues Secret. Wählen Sie zwischen Credentials, Credit-Cards und Notes.</p> | ||||||
|  |             </form> | ||||||
|  |         </> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Home; | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | import { Outlet, Link } from "react-router-dom"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Layout | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | const Layout = ({loginValues}) => { | ||||||
|  |     return ( | ||||||
|  |         <> | ||||||
|  |             <nav> | ||||||
|  |                 <h1>The secret tresor application</h1> | ||||||
|  |                 <p>{loginValues.email === '' ? 'No user logged in' : 'user:' + loginValues.email}</p> | ||||||
|  |                 <ul> | ||||||
|  |                     <li><a href="/">Secrets</a> | ||||||
|  |                     <ul> | ||||||
|  |                         <li><Link to="/secret/secrets">my secrets</Link></li> | ||||||
|  |                         <li><Link to="/secret/newcredential">new credential</Link></li> | ||||||
|  |                         <li><Link to="/secret/newcreditcard">new credit-card</Link></li> | ||||||
|  |                         <li><Link to="/secret/newnote">new note</Link></li> | ||||||
|  |                     </ul> | ||||||
|  |                     </li> | ||||||
|  |                     <li><a href="/">User</a> | ||||||
|  |                     <ul> | ||||||
|  |                         <li><Link to="/user/login">login</Link></li> | ||||||
|  |                         <li><Link to="/user/register">register</Link></li> | ||||||
|  |                     </ul> | ||||||
|  |                     </li> | ||||||
|  |                     <li><a href="/">Admin</a> | ||||||
|  |                         <ul> | ||||||
|  |                             <li><Link to="/user/users">All users</Link></li> | ||||||
|  |                             <li>Add user</li> | ||||||
|  |                             <li><Link to="/user/users/:id">Edit user</Link></li> | ||||||
|  |                             <li>All secrets</li> | ||||||
|  |                         </ul> | ||||||
|  |                     </li> | ||||||
|  |                     <li> | ||||||
|  |                         <Link to="/">About</Link> | ||||||
|  |                     </li> | ||||||
|  |                 </ul> | ||||||
|  |             </nav> | ||||||
|  |             <hr/> | ||||||
|  |             <Outlet/> | ||||||
|  |         </> | ||||||
|  |     ) | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Layout; | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | import '../App.css'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * NoPage | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | const NoPage = () => { | ||||||
|  |     return( | ||||||
|  |         <div> | ||||||
|  |             <h1>404</h1> | ||||||
|  |             <p>Page currently not supported.</p> | ||||||
|  |         </div> | ||||||
|  |     ) | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default NoPage; | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useNavigate } from 'react-router-dom'; | ||||||
|  | import {postSecret} from "../../comunication/FetchSecrets"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * NewCredential | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | function NewCredential({loginValues}) { | ||||||
|  |     const initialState = { | ||||||
|  |         kindid: 1, | ||||||
|  |         kind:"credential", | ||||||
|  |         userName: "", | ||||||
|  |         password: "", | ||||||
|  |         url: "" | ||||||
|  |     }; | ||||||
|  |     const [credentialValues, setCredentialValues] = useState(initialState); | ||||||
|  |     const [errorMessage, setErrorMessage] = useState(''); | ||||||
|  | 
 | ||||||
|  |     const navigate = useNavigate(); | ||||||
|  | 
 | ||||||
|  |     const handleSubmit = async (e) => { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         setErrorMessage(''); | ||||||
|  |         console.log(loginValues) | ||||||
|  |         try { | ||||||
|  |             const content = credentialValues; | ||||||
|  |             await postSecret({loginValues, content}); | ||||||
|  |             setCredentialValues(initialState); | ||||||
|  |             navigate('/secret/secrets'); | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to fetch to server:', error.message); | ||||||
|  |             setErrorMessage(error.message); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <div> | ||||||
|  |             <h2>Add new credential secret</h2> | ||||||
|  |             <form onSubmit={handleSubmit}> | ||||||
|  |                 <section> | ||||||
|  |                     <aside> | ||||||
|  |                         <div> | ||||||
|  |                             <label>username:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={credentialValues.userName} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCredentialValues(prevValues => ({...prevValues, userName: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter username" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>password:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={credentialValues.password} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCredentialValues(prevValues => ({...prevValues, password: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter password" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>url:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={credentialValues.url} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCredentialValues(prevValues => ({...prevValues, url: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter url" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <button type="submit">save secret</button> | ||||||
|  |                         {errorMessage && <p style={{color: 'red'}}>{errorMessage}</p>} | ||||||
|  |                     </aside> | ||||||
|  |                 </section> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default NewCredential; | ||||||
|  | @ -0,0 +1,103 @@ | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useNavigate } from 'react-router-dom'; | ||||||
|  | import {postSecret} from "../../comunication/FetchSecrets"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * NewCreditCard | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | function NewCreditCard({loginValues}) { | ||||||
|  |     const initialState = { | ||||||
|  |         kindid: 2, | ||||||
|  |         kind:"creditcard", | ||||||
|  |         cardtype: "", | ||||||
|  |         cardnumber: "", | ||||||
|  |         expiration: "", | ||||||
|  |         cvv: "" | ||||||
|  |     }; | ||||||
|  |     const [creditCardValues, setCreditCardValues] = useState(initialState); | ||||||
|  |     const [errorMessage, setErrorMessage] = useState(''); | ||||||
|  | 
 | ||||||
|  |     const navigate = useNavigate(); | ||||||
|  | 
 | ||||||
|  |     const handleSubmit = async (e) => { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         setErrorMessage(''); | ||||||
|  |         try { | ||||||
|  |             const content = creditCardValues; | ||||||
|  |             await postSecret({loginValues, content}); | ||||||
|  |             setCreditCardValues(initialState); | ||||||
|  |             navigate('/secret/secrets'); | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to fetch to server:', error.message); | ||||||
|  |             setErrorMessage(error.message); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <div> | ||||||
|  |             <h2>Add new credit-card secret</h2> | ||||||
|  |             <form onSubmit={handleSubmit}> | ||||||
|  |                 <section> | ||||||
|  |                     <aside> | ||||||
|  |                         <div> | ||||||
|  |                             <label>card type:</label> | ||||||
|  |                             <select | ||||||
|  |                                 value={creditCardValues.cardtype} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCreditCardValues((prevValues) => ({ | ||||||
|  |                                         ...prevValues, | ||||||
|  |                                         cardtype: e.target.value, | ||||||
|  |                                     }))} | ||||||
|  |                                 required | ||||||
|  |                             > | ||||||
|  |                                 <option value="" disabled> | ||||||
|  |                                     Please select card type | ||||||
|  |                                 </option> | ||||||
|  |                                 <option value="Visa">Visa</option> | ||||||
|  |                                 <option value="Mastercard">Mastercard</option> | ||||||
|  |                             </select> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>cardnumber:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={creditCardValues.cardnumber} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCreditCardValues(prevValues => ({...prevValues, cardnumber: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter cardnumber" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>expiration (mm/yy):</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={creditCardValues.expiration} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCreditCardValues(prevValues => ({...prevValues, expiration: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter expiration" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>cvv:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={creditCardValues.cvv} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCreditCardValues(prevValues => ({...prevValues, cvv: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter cvv" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <button type="submit">Save secret</button> | ||||||
|  |                         {errorMessage && <p style={{color: 'red'}}>{errorMessage}</p>} | ||||||
|  |                     </aside> | ||||||
|  |                 </section> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default NewCreditCard; | ||||||
|  | @ -0,0 +1,79 @@ | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useNavigate } from 'react-router-dom'; | ||||||
|  | import {postSecret} from "../../comunication/FetchSecrets"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * NewNote | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | function NewNote({loginValues}) { | ||||||
|  |     const initialState = { | ||||||
|  |         kindid: 3, | ||||||
|  |         kind:"note", | ||||||
|  |         title: "", | ||||||
|  |         content: "", | ||||||
|  |     }; | ||||||
|  |     const [noteValues, setNoteValues] = useState(initialState); | ||||||
|  |     const [errorMessage, setErrorMessage] = useState(''); | ||||||
|  | 
 | ||||||
|  |     const navigate = useNavigate(); | ||||||
|  | 
 | ||||||
|  |     const handleSubmit = async (e) => { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         setErrorMessage(''); | ||||||
|  |         try { | ||||||
|  |             const content = noteValues; | ||||||
|  |             await postSecret({loginValues, content}); | ||||||
|  |             setNoteValues(initialState); | ||||||
|  |             navigate('/secret/secrets'); | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to fetch to server:', error.message); | ||||||
|  |             setErrorMessage(error.message); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <div> | ||||||
|  |             <h2>Add new note secret</h2> | ||||||
|  |             <form onSubmit={handleSubmit}> | ||||||
|  |                 <section> | ||||||
|  |                     <aside> | ||||||
|  |                         <div> | ||||||
|  |                             <label>title:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={noteValues.title} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setNoteValues(prevValues => ({...prevValues, title: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter a title *" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>content:</label> | ||||||
|  |                             <textarea | ||||||
|  |                                 rows={4} | ||||||
|  |                                 style={{ | ||||||
|  |                                     resize: 'both', // Ermöglicht Größenänderung in beide Richtungen
 | ||||||
|  |                                     width: '24%', // Standardbreite (kann angepasst werden)
 | ||||||
|  |                                     minWidth: '190px', // Minimale Breite
 | ||||||
|  |                                     minHeight: '100px', // Minimale Höhe
 | ||||||
|  |                                 }} | ||||||
|  |                                 value={noteValues.content} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setNoteValues(prevValues => | ||||||
|  |                                         ({...prevValues, content: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter a content *" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <button type="submit">Save secret</button> | ||||||
|  |                         {errorMessage && <p style={{color: 'red'}}>{errorMessage}</p>} | ||||||
|  |                     </aside> | ||||||
|  |                 </section> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default NewNote; | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | import '../../App.css'; | ||||||
|  | import React, {useEffect, useState} from 'react'; | ||||||
|  | import {getSecretsforUser} from "../../comunication/FetchSecrets"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Secrets | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | const Secrets = ({loginValues}) => { | ||||||
|  |     const [secrets, setSecrets] = useState([]); | ||||||
|  |     const [errorMessage, setErrorMessage] = useState(''); | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         const fetchSecrets = async () => { | ||||||
|  |             setErrorMessage(''); | ||||||
|  |             if( ! loginValues.email){ | ||||||
|  |                 console.error('Secrets: No valid email, please do login first:' + loginValues); | ||||||
|  |                 setErrorMessage("No valid email, please do login first."); | ||||||
|  |             } else { | ||||||
|  |                 try { | ||||||
|  |                     const data = await getSecretsforUser(loginValues); | ||||||
|  |                     console.log(data); | ||||||
|  |                     setSecrets(data); | ||||||
|  |                 } catch (error) { | ||||||
|  |                     console.error('Failed to fetch to server:', error.message); | ||||||
|  |                     setErrorMessage(error.message); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         fetchSecrets(); | ||||||
|  |     }, [loginValues]); | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <> | ||||||
|  |             <h1>my secrets</h1> | ||||||
|  |             {errorMessage && <p style={{color: 'red'}}>{errorMessage}</p>} | ||||||
|  |              <form> | ||||||
|  |                 <h2>secrets</h2> | ||||||
|  |                 <table border="1"> | ||||||
|  |                     <thead> | ||||||
|  |                     <tr> | ||||||
|  |                         <th>secret id</th> | ||||||
|  |                         <th>user id</th> | ||||||
|  |                         <th>content</th> | ||||||
|  |                     </tr> | ||||||
|  |                     </thead> | ||||||
|  |                     <tbody> | ||||||
|  |                     {secrets?.length > 0 ? ( | ||||||
|  |                         secrets.map(secret => ( | ||||||
|  |                             <tr key={secret.id}> | ||||||
|  |                                 <td>{secret.id}</td> | ||||||
|  |                                 <td>{secret.userId}</td> | ||||||
|  |                                 <td> | ||||||
|  |                                     <pre>{JSON.stringify(secret.content, null, 2)}</pre> | ||||||
|  |                                 </td> | ||||||
|  |                             </tr> | ||||||
|  |                         )) | ||||||
|  |                     ) : ( | ||||||
|  |                         <tr> | ||||||
|  |                             <td colSpan="3">No secrets available</td> | ||||||
|  |                         </tr> | ||||||
|  |                     )} | ||||||
|  |                     </tbody> | ||||||
|  |                 </table> | ||||||
|  |             </form> | ||||||
|  |         </> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Secrets; | ||||||
|  | @ -0,0 +1,52 @@ | ||||||
|  | import { useNavigate } from 'react-router-dom'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * LoginUser | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | function LoginUser({loginValues, setLoginValues}) { | ||||||
|  |     const navigate = useNavigate(); | ||||||
|  | 
 | ||||||
|  |     const handleSubmit = async (e) => { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         console.log(loginValues); | ||||||
|  |         navigate('/') | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <div> | ||||||
|  |             <h2>Login user</h2> | ||||||
|  |             <form onSubmit={handleSubmit}> | ||||||
|  |                 <section> | ||||||
|  |                     <aside> | ||||||
|  |                         <div> | ||||||
|  |                             <label>Email:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={loginValues.email} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setLoginValues(prevValues => ({...prevValues, email: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter your email *" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>Password:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={loginValues.password} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setLoginValues(prevValues => ({...prevValues, password: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter your password *" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                     </aside> | ||||||
|  |                 </section> | ||||||
|  |                 <button type="submit">Login</button> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default LoginUser; | ||||||
|  | @ -0,0 +1,117 @@ | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { useNavigate } from 'react-router-dom'; | ||||||
|  | import {postUser} from "../../comunication/FetchUser"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * RegisterUser | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | function RegisterUser({loginValues, setLoginValues}) { | ||||||
|  |     const navigate = useNavigate(); | ||||||
|  | 
 | ||||||
|  |     const initialState = { | ||||||
|  |         firstName: "", | ||||||
|  |         lastName: "", | ||||||
|  |         email: "", | ||||||
|  |         password: "", | ||||||
|  |         passwordConfirmation: "", | ||||||
|  |         errorMessage: "" | ||||||
|  |     }; | ||||||
|  |     const [credentials, setCredentials] = useState(initialState); | ||||||
|  |     const [errorMessage, setErrorMessage] = useState(''); | ||||||
|  | 
 | ||||||
|  |     const handleSubmit = async (e) => { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         setErrorMessage(''); | ||||||
|  | 
 | ||||||
|  |         //validate
 | ||||||
|  |         if(credentials.password !== credentials.passwordConfirmation) { | ||||||
|  |             console.log("password != passwordConfirmation"); | ||||||
|  |             setErrorMessage('Password and password-confirmation are not equal.'); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             await postUser(credentials); | ||||||
|  |             setLoginValues({userName: credentials.email, password: credentials.password}); | ||||||
|  |             setCredentials(initialState); | ||||||
|  |             navigate('/'); | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error('Failed to fetch to server:', error.message); | ||||||
|  |             setErrorMessage(error.message); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <div> | ||||||
|  |             <h2>Register user</h2> | ||||||
|  |             <form onSubmit={handleSubmit}> | ||||||
|  |                 <section> | ||||||
|  |                 <aside> | ||||||
|  |                     <div> | ||||||
|  |                         <label>Firstname:</label> | ||||||
|  |                         <input | ||||||
|  |                             type="text" | ||||||
|  |                             value={credentials.firstName} | ||||||
|  |                             onChange={(e) => | ||||||
|  |                                 setCredentials(prevValues => ({...prevValues, firstName: e.target.value}))} | ||||||
|  |                             required | ||||||
|  |                             placeholder="Please enter your firstname *" | ||||||
|  |                         /> | ||||||
|  |                     </div> | ||||||
|  |                     <div> | ||||||
|  |                         <label>Lastname:</label> | ||||||
|  |                         <input | ||||||
|  |                             type="text" | ||||||
|  |                             value={credentials.lastName} | ||||||
|  |                             onChange={(e) => | ||||||
|  |                                 setCredentials(prevValues => ({...prevValues, lastName: e.target.value}))} | ||||||
|  |                             required | ||||||
|  |                             placeholder="Please enter your lastname *" | ||||||
|  |                         /> | ||||||
|  |                     </div> | ||||||
|  |                     <div> | ||||||
|  |                         <label>Email:</label> | ||||||
|  |                         <input | ||||||
|  |                             type="text" | ||||||
|  |                             value={credentials.email} | ||||||
|  |                             onChange={(e) => | ||||||
|  |                                 setCredentials(prevValues => ({...prevValues, email: e.target.value}))} | ||||||
|  |                             required | ||||||
|  |                             placeholder="Please enter your email" | ||||||
|  |                         /> | ||||||
|  |                     </div> | ||||||
|  |                 </aside> | ||||||
|  |                     <aside> | ||||||
|  |                         <div> | ||||||
|  |                             <label>Password:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={credentials.password} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCredentials(prevValues => ({...prevValues, password: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please enter your pwd *" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                         <div> | ||||||
|  |                             <label>Password confirmation:</label> | ||||||
|  |                             <input | ||||||
|  |                                 type="text" | ||||||
|  |                                 value={credentials.passwordConfirmation} | ||||||
|  |                                 onChange={(e) => | ||||||
|  |                                     setCredentials(prevValues => ({...prevValues, passwordConfirmation: e.target.value}))} | ||||||
|  |                                 required | ||||||
|  |                                 placeholder="Please confirm your pwd *" | ||||||
|  |                             /> | ||||||
|  |                         </div> | ||||||
|  |                     </aside> | ||||||
|  |                 </section> | ||||||
|  |                 <button type="submit">Register</button> | ||||||
|  |                 {errorMessage && <p style={{ color: 'red' }}>{errorMessage}</p>} | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default RegisterUser; | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | import '../../App.css'; | ||||||
|  | import React, {useEffect, useState} from "react"; | ||||||
|  | import {getUsers} from "../../comunication/FetchUser"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Users | ||||||
|  |  * @author Peter Rutschmann | ||||||
|  |  */ | ||||||
|  | const Users = ({loginValues}) => { | ||||||
|  |     const [users, setUsers] = useState([]); | ||||||
|  |     const [errorMessage, setErrorMessage] = useState(''); | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         const fetchUsers = async () => { | ||||||
|  |             try { | ||||||
|  |                 const users = await getUsers(); | ||||||
|  |                 console.log(users); | ||||||
|  |                 setUsers(users); | ||||||
|  |             } catch (error) { | ||||||
|  |                 console.error('Failed to fetch to server:', error.message); | ||||||
|  |                 setErrorMessage(error.message); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         fetchUsers(); | ||||||
|  |     }, [loginValues]); | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <> | ||||||
|  |             <h1>Client list</h1> | ||||||
|  |             <ul> | ||||||
|  |                 {users.map(user => ( | ||||||
|  |                     <li key={user.id}>{user.id} {user.firstName} {user.lastName} - {user.email} - {user.password}</li> | ||||||
|  |                 ))} | ||||||
|  |             </ul> | ||||||
|  |             {errorMessage && <p style={{ color: 'red' }}>{errorMessage}</p>} | ||||||
|  |         </> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default Users; | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | const reportWebVitals = onPerfEntry => { | ||||||
|  |   if (onPerfEntry && onPerfEntry instanceof Function) { | ||||||
|  |     import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { | ||||||
|  |       getCLS(onPerfEntry); | ||||||
|  |       getFID(onPerfEntry); | ||||||
|  |       getFCP(onPerfEntry); | ||||||
|  |       getLCP(onPerfEntry); | ||||||
|  |       getTTFB(onPerfEntry); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default reportWebVitals; | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | // jest-dom adds custom jest matchers for asserting on DOM nodes.
 | ||||||
|  | // allows you to do things like:
 | ||||||
|  | // expect(element).toHaveTextContent(/react/i)
 | ||||||
|  | // learn more: https://github.com/testing-library/jest-dom
 | ||||||
|  | import '@testing-library/jest-dom'; | ||||||
		Loading…
	
		Reference in New Issue