diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..a385a55eb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,682 @@
+HELP.md
+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/
+
+
+# Created by https://www.toptal.com/developers/gitignore/api/netbeans,intellij,eclipse,visualstudiocode,notepadpp,sublimetext,angular,node,react,reactnative,java
+# Edit at https://www.toptal.com/developers/gitignore?templates=netbeans,intellij,eclipse,visualstudiocode,notepadpp,sublimetext,angular,node,react,reactnative,java
+
+### Angular ###
+## Angular ##
+# compiled output
+dist/
+tmp/
+app/**/*.js
+app/**/*.js.map
+
+# dependencies
+node_modules/
+bower_components/
+
+# IDEs and editors
+.idea/
+
+# misc
+.sass-cache/
+connect.lock/
+coverage/
+libpeerconnection.log/
+npm-debug.log
+testem.log
+typings/
+
+# e2e
+e2e/*.js
+e2e/*.map
+
+# System Files
+.DS_Store/
+
+### Eclipse ###
+.metadata
+bin/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Eclipse Patch ###
+# Spring Boot Tooling
+.sts4-cache/
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### NetBeans ###
+**/nbproject/private/
+**/nbproject/Makefile-*.mk
+**/nbproject/Package-*.bash
+build/
+nbbuild/
+nbdist/
+.nb-gradle/
+
+### Node ###
+# Logs
+logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+jspm_packages/
+
+# TypeScript v1 declaration files
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+### NotepadPP ###
+# Notepad++ backups #
+
+### react ###
+.DS_*
+**/*.backup.*
+**/*.back.*
+
+node_modules
+
+*.sublime*
+
+psd
+thumb
+sketch
+
+### ReactNative ###
+# React Native Stack Base
+
+.expo
+__generated__
+
+### ReactNative.Android Stack ###
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+
+# Generated files
+gen/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+
+# Local configuration file (sdk path, etc)
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
+
+### ReactNative.Buck Stack ###
+buck-out/
+.buckconfig.local
+.buckd/
+.buckversion
+.fakebuckversion
+
+### ReactNative.Gradle Stack ###
+.gradle
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### ReactNative.Linux Stack ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### ReactNative.Node Stack ###
+# Logs
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+
+# Runtime data
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+
+# Coverage directory used by tools like istanbul
+
+# nyc test coverage
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
+# Bower dependency directory (https://bower.io/)
+
+# node-waf configuration
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+
+# Dependency directories
+
+# TypeScript v1 declaration files
+
+# TypeScript cache
+
+# Optional npm cache directory
+
+# Optional eslint cache
+
+# Microbundle cache
+
+# Optional REPL history
+
+# Output of 'npm pack'
+
+# Yarn Integrity file
+
+# dotenv environment variables file
+
+# parcel-bundler cache (https://parceljs.org/)
+
+# Next.js build output
+
+# Nuxt.js build / generate output
+
+# Gatsby files
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+
+# Serverless directories
+
+# FuseBox cache
+
+# DynamoDB Local files
+
+# TernJS port file
+
+# Stores VSCode versions used for testing VSCode extensions
+
+### ReactNative.Xcode Stack ###
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+DerivedData/
+*.moved-aside
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+
+## Gcc Patch
+/*.gcno
+
+### ReactNative.macOS Stack ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### SublimeText ###
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+
+# End of https://www.toptal.com/developers/gitignore/api/netbeans,intellij,eclipse,visualstudiocode,notepadpp,sublimetext,angular,node,react,reactnative,java
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 000000000..e76d1f324
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed 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.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..642d572ce
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/0.PNG b/0.PNG
new file mode 100644
index 000000000..119a54fb6
Binary files /dev/null and b/0.PNG differ
diff --git a/1.png b/1.png
new file mode 100644
index 000000000..48c5474ff
Binary files /dev/null and b/1.png differ
diff --git a/10.jpg b/10.jpg
new file mode 100644
index 000000000..c17746be4
Binary files /dev/null and b/10.jpg differ
diff --git a/11.jpg b/11.jpg
new file mode 100644
index 000000000..a745bfe5b
Binary files /dev/null and b/11.jpg differ
diff --git a/12.jpg b/12.jpg
new file mode 100644
index 000000000..0b0d7d7d7
Binary files /dev/null and b/12.jpg differ
diff --git a/13.jpg b/13.jpg
new file mode 100644
index 000000000..0062ec243
Binary files /dev/null and b/13.jpg differ
diff --git a/14.jpg b/14.jpg
new file mode 100644
index 000000000..d9cd038e3
Binary files /dev/null and b/14.jpg differ
diff --git a/2.PNG b/2.PNG
new file mode 100644
index 000000000..d440c839a
Binary files /dev/null and b/2.PNG differ
diff --git a/3.PNG b/3.PNG
new file mode 100644
index 000000000..2b526e9f3
Binary files /dev/null and b/3.PNG differ
diff --git a/4.PNG b/4.PNG
new file mode 100644
index 000000000..166e13bb8
Binary files /dev/null and b/4.PNG differ
diff --git a/5.jpg b/5.jpg
new file mode 100644
index 000000000..945020ba7
Binary files /dev/null and b/5.jpg differ
diff --git a/6.jpg b/6.jpg
new file mode 100644
index 000000000..2b3877ddc
Binary files /dev/null and b/6.jpg differ
diff --git a/7.jpg b/7.jpg
new file mode 100644
index 000000000..ffb88ab03
Binary files /dev/null and b/7.jpg differ
diff --git a/8.jpg b/8.jpg
new file mode 100644
index 000000000..9065e283e
Binary files /dev/null and b/8.jpg differ
diff --git a/9.jpg b/9.jpg
new file mode 100644
index 000000000..50a2bcc3f
Binary files /dev/null and b/9.jpg differ
diff --git a/README.md b/README.md
index 5c0a1e7fa..453c3241f 100644
--- a/README.md
+++ b/README.md
@@ -1,71 +1,70 @@
-# Full Stack Web Application
+# Case Study Submission
-* **Objective** - to create an implementation of a web service
-* **Purpose** - to demonstrate the construction of a full-stacked web-application
-* **Description**
- * This Case Study is your first foray into building a full-stack application. You'll be building a Spring MVC based application, which means you'll learn about what it takes to build a functional application from the ground up yourself.
- * This is exciting! It's a lot, but we've given you the tools to be able to build what you need, and you get to decide what you do with it. You also get to be creative in choosing what sort of application you want to build!
- * You will be working individually to design your app. We hope you'll exercise creativity on this project, sketch some wireframes before you start, make sure you have time to run these ideas by your instructors to get their feedback before you dive too deep into coding! Remember to keep things small and focus on mastering the fundamentals.
-* **Additional Resources**
- * [The Original Case Study Document](./case-study.pdf)
- * [Case Study Outline](./case-study-outline.pdf)
- * [Case Study Deliverables](./README_deliverables.md)
- * [Identifying Plagiarism](./README_plagiarism.md)
- * [Suggested Project Topics](./README_suggested-project-topics.md)
+## Project Overview
+The purpose of this project is to satisfy the requirements for graduation from Per Scholas program.
+This application allows a user to make an account or login to an existing account and view stock market information. The user selects which companies to view information about and an API call is made to return a JSON object to this application, where the information is presented to the user.
+# How to setup
+Step 1: Navigate to https://github.com/nick-devos/casestudy.fullstack_webapplication and clone the project.
+
-## Minimum Features
-* `RESTful` web service which consumes requests from a front-end web application and caches each request and the respective response to a database.
-* The application must support a login functionality.
+Use your `git bash` terminal to clone a copy of the project to the local directory of your choosing.
+
+Step 2: This project uses MySql to persist login credentials and requires some configuration to do so. First, navigate to your MySql Workbench and take note of the username, password (will not be shown), and host location needed to to access the DBMS.
+
+We will need to create a database for the program to use, so enter the DBMS and create a database by writing one simple statement. Here I have chosen to call the database `casestudy`. So, my statment is `create database casestudy;` Execute the statement and make sure it is successful by checking the output.
+
-## Developmental Notes
+Step 3: Now we will make sure the program points to the correct database. Navigate to the project directory and open the `pom.xml` file in your IDE.
+
-### Tech Stack Selection
-* Select at least 1 technology from each of the following categories:
- * **Version Control System**
- 1. Github
- 2. Bitbucket
-
- * **Wireframe**
- 1. Mockflow
- 2. Balsamiq
- 3. Lucidcharts
+Once the project is built, navigate to the `application.properties`. If needed, change the `spring.datasource.url`, `spring.datasource.username`, and `spring.datasource.password` to reflect your own settings noted in step 2.
+
- * **Frontend**
- 1. Angular
- 2. React
- 3. Vue.JS
-
- * **Business Logic**
- 1. Java
- 2. TypeScript
+Step 4: Run the project. This can be accomplished by pressing `Shift + F10` if you're on a PC. Alternatively, in the menu on top of the window, click `Run` → `Run 'CasestudyApplication'`. If you have maven installed, you can use the command `mvn spring-boot:run`. If something is already running on port 8080, run the `kill-8080.bat` or `kill-8080.sh` included in the project files. Ensure that the project begins running.
+
- * **WebServer Implementation**
- 1. Spring Boot
- 2. At least 1 [backing service](https://12factor.net/backing-services) API
+Step 5: Open an internet browser and navigate to `localhost:8080/login`. This is where you will login after creating an account. Click `Create an account` under the login button. This will redirect you to the registration page. Create a username and password (username must be 6-32 characters, password must be at least 8 characters).
+
- * **Data Layer**
- 1. MySQL
- 2. PostgreSQL
- 3. MariaDB
+Upon successful creation of a user, you will be redirected to the welcome screen.
+
- * **Web Server Cloud Deployment**
- 1. Heroku
- 2. AWS EC2 Instance
-
- * **Web Application Cloud Deployment**
- 1. Netlify
- 2. AWS EC2 Instance
+From here you can use the links in the navbar to navigate to the pages available to logged-in users.
+# Exploring the program
+Market News: The market news page allows a user to see the 20 most recent news articles for the entire market provided by Finnhub's API.
+
+Company Overview: The company overview page is the most complex and powerful page of the project. Upon navigating to the page, it is auto-populated with data for Amazon.
+
-### Installation
-* It is advised that you make install each of the following technologies to ensure that are at least accessible
- * Install [NodeJs](https://nodejs.org/en/).
- * Install [Angular](http://angular.io/).
- * Install [AngularCli](https://cli.angular.io/).
+However, a user may use the search bar at the top to enter the symbol/ticker of a desired stock, such as 'TSLA' for Tesla, and the page will show data for that particular stock. This page uses 3 seperate API's to populate data (Finnhub, AlphaVantage, and IEXCloud).
+
+
+One of the data points provided is a 5 day price chart of the stock price of the security. This is done using the Chart.js open-source library. (Note: Sometimes the chart fails to render on load, however if you resize the window, the chart will populate with data.)
+
+
+Under the chart is the company's description and general information about the company, financial information, and dividend information.
+
+
+On the bottom of the page is news articles directly related to the security specified. The program is designed to fetch the last month's worth of articles however, it also displays a max of 21 articles so you don't have hundreds of articles on one page for companies like Amazon with large quantities of articles.
+
+When you're done using the program just click the `Logout` link at the top right of the window (far right of the navbar) and you will be logged out and redirected to the login page, where you will see a message displaying that you have successfully logged out.
+
+
+# How it works
+Due to the constant changing of information in the stock market, the stock data is not persisted into the database. Each time you load the market news page one API call is made to Finnhub. Loading the company overview page and each subsequent change of stock symbols makes a total of 4 API calls (AlphaVantage: 1, Finnhub:1, IEXCloud: 2). Since data is not being persisted to the database, API calls are made in JavaScript and displayed in HTML. JavaScript receives JSON data from the external API's and uses templates created by me to display the parsed JSON elements in their desired locations. Due to the use of free tokens from API providers, you may hit the limit for API calls and get an error instead of receiving company data if too many API calls are made in a short period of time.
+
+Login functionality is made possible with Spring Security. The program uses custom login and registration JSP pages to gather information from the user.
+
+###### Technologies used
+* Spring MVC → Java
+* JSP → HTML, CSS, Js
+* MySQL
+* Git
\ No newline at end of file
diff --git a/README_deliverables.md b/README_deliverables.md
deleted file mode 100644
index 4bb20a97f..000000000
--- a/README_deliverables.md
+++ /dev/null
@@ -1,52 +0,0 @@
-## Deliverables
-
-### Trello Board
-* To begin your case study, create a Trello board
-* Please ensure the instructor has access to this Trello board.
-* This board will be used to track progress and evaluate required effort of tasks.
-* Ensure the board has at least 5 columns accounting for each of the following
- * `Ideation`
- * This column will first contain cards describing a potential application to be built.
- * Upon concept approval, this column will be re-purposed to describe potential features of the approved application.
- * `Backlog`
- * This column will contain cards describing work that **will not be** done in a near-future, but **must be done** eventually for the product to be considered completed
- * `Upcoming`
- * This column will contain cards describing work that **will not be** done in the current-sprint commitment, but **will be committed to** in the upcoming-sprint commitment.
- * `Current`
- * This column will contain cards describing work that **will be** done in the current-sprint commitment.
- * `In Review
- * This column will contain cards describing work that **need to be reviewed** to be considered **done**.
- * `Completed`
- * This column will contain cards describing work that has been reviewed and is considered completed
-
-### Database
-* MariaDB is the suggested dialect
-* This deliverable includes creating a database that reflects the website you have decided to create
-* Schema diagram: a visual representation of your database
-* Database file creation (`.sql`): Queries used to create the database
-
-
-### Core Java & JPA
-* This deliverable includes creating a back-end environment that is composed of different Java classes
- * Models: Java classes that represent an entity and are used to transfer data related to an entity, create multiple queries, and represent the database as an object-oriented model
- * Persistence.xml: This file configures the Java classes that are going to be using JPA to interact with the database.
- * Persistence Java Class: A static class that allows the application to create a persistent object which can be used to interact with the database.
- * Service Class or Data Access Objects (DAO): Java classes that are composed of one or more functions and have direct access to the database by using JPA persistent object. Each function in a DAO class interacts with the database differently.
- * Custom Exceptions: Java classes that allow you to describe an error while the application is running.
- * Utilizes: Java classes that hold constant variables (Variables that never change from its initial value). The value of these variables can be requested parameters, Database queries used in the DAO, name of HTML pages, or URL patterns to forward a request to.
-
-
-### HTML5/CSS3
-* This deliverable includes creating every page required by the given case study
-* HTML5: Use HTML for static and dynamic pages and markup the structure of every page.
-* CSS3: Use CSS3 to style your HTML pages and make sure to take into consideration the knowledge acquired from the visual design lessons.
-
-
-### Spring MVC
-* This deliverable includes connecting no. 1, 2 and 3 deliverables to function together
- * Spring MVC: Responsible for responding to a request made by the user. This can be login, registration, etc. When using Spring MVC make sure to use at least the following functionalities: different type of session management, annotation-based controller, exception handling, models, model attributes.
-
-
-### Junit (Test all DAO classes)
-* This deliverable includes creating a test class for each DAO available and creating a test case in the test class for each function inside the DAO
- * Junit: A Java framework responsible for performing unit testing against every DAO class available. There should be a test class for every DAO and inside the test classes, there should be at least one test case for every function inside the DAO classes. When using JUnit make sure to use the following functionalities: Suite classes, Runner, Feature life cycle, Test, Parameterized classes, Java Hamcrest library.
diff --git a/README_plagiarism.md b/README_plagiarism.md
deleted file mode 100644
index 5b071dae3..000000000
--- a/README_plagiarism.md
+++ /dev/null
@@ -1,20 +0,0 @@
-### A Note on Plagiarism
-* You are encouraged to ask others, including students, instructors, and StackOverflow, for help. However, it is NOT ACCEPTABLE TO COPY another person's code and submit it as your own. More importantly, it is detrimental to your learning and growth.
-* All of the following are considered plagiarism or cheating:
- * Turning in work that is not your own.
- * Turning in someone else's work as your own.
- * Hiring, or paying someone to do your work for you.
- * Copying words or code without giving credit.
- * Building or copying someone else’s idea without their knowledge or giving credit.
- * Giving incorrect information about a source.
- * Changing words, variable names, etc. but copying the code or files of a source without giving credit.
- * Copying so many ideas or code blocks from a source that it makes up the majority of your work, whether you give credit or not.
- * Failing to put a quotation in quotation marks.
-
-
-* In an effort to not plagiarize, credit for this content goes to:
-* [Plagiarism.org](plagiarism.org), specifically the “plagiarism 101” section. The content was adapted for the code. For more information, please see:
- * [What is Plagiarism](http://www.plagiarism.org/plagiarism-101/what-is-plagiarism)
- * [Types of Plagiarism](http://www.plagiarism.org/plagiarism-101/types-of-plagiarism)
- * [How do I safely write code in my own 'words' and not plagiarize?](http://programmers.stackexchange.com/questions/80167/how-do-i-safely-write-code-in-my-own-words-and-not-plagiarize)
- * [Avoiding Plagiarism: Writing Computer Code](http://www.upenn.edu/academicintegrity/ai_computercode.html)
diff --git a/README_suggested-project-topics.md b/README_suggested-project-topics.md
deleted file mode 100644
index 9b2ac965e..000000000
--- a/README_suggested-project-topics.md
+++ /dev/null
@@ -1,54 +0,0 @@
-
-## Suggested Project Topics
-
-### (Suggested Project Topic 1) TCP Application
-
-#### User Stories to Fulfill
-* As a client, (not logged in) I
- * can send messages to a _peer_.
- * can view default channels
- * can view all accessible channels
- * can view messages live as they are received
-
-
-
-
-
-
-
-### (Suggested Project Topic 2) Blog Application
-
-#### User Stories to Fulfill
-* As a client, (not logged in) I
- * can create new blog posts
- * A blog post can consist of images and text
- * can view list of all blog posts
- * can view blog posts filtered by blog-tag
- * can view new blog posts upon refreshing the DOM
-
-
-
-
-
-
-
-### (Suggested Project Topic 3) Application
-
-#### User Stories to Fulfill
-* As a client, (not logged in) I
- * can upload new videos
- * can view list of all videos
- * can post simple text-comments on a video
-
-
-
-
-
-### (Suggested Project Topic 4) Money Management Application
-
-#### User Stories to Fulfill
-* As a client, (not logged in) I
- * can create new accounts
- * deposit money to each account
- * withdrawl money from each account
- * transfer money to and from any 2 accounts
diff --git a/case-study-outline.pdf b/case-study-outline.pdf
deleted file mode 100644
index 94edbd7e7..000000000
Binary files a/case-study-outline.pdf and /dev/null differ
diff --git a/case-study.pdf b/case-study.pdf
deleted file mode 100644
index 8aa8a71cb..000000000
Binary files a/case-study.pdf and /dev/null differ
diff --git a/kill-8080.bat b/kill-8080.bat
new file mode 100644
index 000000000..44b34ddda
--- /dev/null
+++ b/kill-8080.bat
@@ -0,0 +1 @@
+for /f "tokens=5" %%a in ('netstat -aon ^| find ":8080" ^| find "LISTENING"') do taskkill /f /pid %%a
\ No newline at end of file
diff --git a/kill-8080.sh b/kill-8080.sh
new file mode 100644
index 000000000..1c5b67ddd
--- /dev/null
+++ b/kill-8080.sh
@@ -0,0 +1 @@
+kill -kill `lsof -t -i tcp:8080
\ No newline at end of file
diff --git a/kill-8080.txt b/kill-8080.txt
new file mode 100644
index 000000000..8c9e54694
--- /dev/null
+++ b/kill-8080.txt
@@ -0,0 +1,9 @@
+kill-8080.bat and kill-8080.sh would not stop the process on port 8080
+
+instead I had to use these commands in cmd while running as an administrator ->
+
+`netstat -ano | findstr :8080` -> to find the pid
+`taskkill /f /pid {pid#}` -> use the pid number to kill the task on port 8080
+
+if successful, the following response will be returned ->
+SUCCESS: The process with PID 3920 has been terminated.
\ No newline at end of file
diff --git a/mvnw b/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,310 @@
+#!/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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# 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 /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
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -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 "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; 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="`which 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
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# 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/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# 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.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+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 "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -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
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,182 @@
+@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 Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@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 "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\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 DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_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 DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_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('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@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 "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\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%
+
+exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..07258de31
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,93 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.3.3.RELEASE
+
+
+ com.devos.perscholas
+ casestudy
+ 0.0.1-SNAPSHOT
+ casestudy
+ Stock market information presented to a user about companies that the user chooses to watch
+
+
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.11.0
+
+
+ javax.servlet
+ jstl
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+
+
+
+ org.eclipse.jdt.core.compiler
+ ecj
+ 4.6.1
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/src/main/java/com/devos/perscholas/casestudy/CasestudyApplication.java b/src/main/java/com/devos/perscholas/casestudy/CasestudyApplication.java
new file mode 100644
index 000000000..cb509321b
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/CasestudyApplication.java
@@ -0,0 +1,14 @@
+package com.devos.perscholas.casestudy;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@SpringBootApplication
+public class CasestudyApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CasestudyApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/config/WebSecurityConfig.java b/src/main/java/com/devos/perscholas/casestudy/config/WebSecurityConfig.java
new file mode 100644
index 000000000..03fda292a
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/config/WebSecurityConfig.java
@@ -0,0 +1,53 @@
+package com.devos.perscholas.casestudy.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @Bean
+ public BCryptPasswordEncoder bCryptPasswordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .antMatchers("/resources/**", "/registration").permitAll()
+ .anyRequest().authenticated()
+ .and()
+ .formLogin()
+ .loginPage("/login")
+ .permitAll()
+ .and()
+ .logout()
+ .permitAll();
+ }
+
+ @Override
+ @Bean
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ auth
+ .parentAuthenticationManager(authenticationManagerBean())
+ .userDetailsService(userDetailsService)
+ .passwordEncoder(bCryptPasswordEncoder());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/devos/perscholas/casestudy/controller/UserProfileController.java b/src/main/java/com/devos/perscholas/casestudy/controller/UserProfileController.java
new file mode 100644
index 000000000..119407949
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/controller/UserProfileController.java
@@ -0,0 +1,62 @@
+package com.devos.perscholas.casestudy.controller;
+
+
+import com.devos.perscholas.casestudy.model.UserProfile;
+import com.devos.perscholas.casestudy.service.SecurityServiceImpl;
+import com.devos.perscholas.casestudy.service.UserProfileService;
+import com.devos.perscholas.casestudy.validator.UserProfileValidator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+
+@Controller
+public class UserProfileController {
+ private UserProfileService userService;
+ private SecurityServiceImpl securityService;
+ private UserProfileValidator userValidator;
+
+ @Autowired
+ public UserProfileController(UserProfileService userService, UserProfileValidator userValidator, SecurityServiceImpl securityService) {
+ this.userService = userService;
+ this.userValidator = userValidator;
+ this.securityService = securityService;
+ }
+
+ @GetMapping(value = "/registration")
+ public String registration(Model model) {
+ model.addAttribute("userForm", new UserProfile());
+ return "registration";
+ }
+
+ @PostMapping(value = "/registration")
+ public String registration(@ModelAttribute("userForm") UserProfile userForm, BindingResult bindingResult, Model model) {
+ userValidator.validate(userForm, bindingResult);
+ if (bindingResult.hasErrors()) {
+ return "registration";
+ }
+ userService.save(userForm);
+ securityService.autologin(userForm.getUsername(), userForm.getPasswordConfirm());
+ return "redirect:/welcome";
+ }
+
+ @GetMapping(value = "/login")
+ public String login(Model model, String error, String logout) {
+ if (error != null) {
+ model.addAttribute("error", "Your username and password are invalid.");
+ }
+ if (logout != null) {
+ model.addAttribute("message", "You have logged out successfully.");
+ }
+
+ return "login";
+ }
+
+ @GetMapping(value = {"/", "/welcome"})
+ public String welcome(Model model) {
+ return "welcome";
+ }
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/controller/ViewController.java b/src/main/java/com/devos/perscholas/casestudy/controller/ViewController.java
new file mode 100644
index 000000000..8c13fa35a
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/controller/ViewController.java
@@ -0,0 +1,31 @@
+package com.devos.perscholas.casestudy.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/")
+public class ViewController {
+// @GetMapping
+// public String getLogin(Model model) {
+// return "login";
+// }
+
+// @GetMapping("user-profile/{id}")
+// public String getView(Model model, @PathVariable Long id) {
+// return "profile";
+// }
+
+ @GetMapping("market-news")
+ public String getMarketNews(Model model) {
+ return "market-news";
+ }
+
+ @GetMapping("company-overview")
+ public String getCompanyOverview(Model model) {
+ return "company-overview";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/devos/perscholas/casestudy/model/UserProfile.java b/src/main/java/com/devos/perscholas/casestudy/model/UserProfile.java
new file mode 100644
index 000000000..d4da724bf
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/model/UserProfile.java
@@ -0,0 +1,60 @@
+package com.devos.perscholas.casestudy.model;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Entity
+public class UserProfile {
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String username;
+ private String password;
+
+ @Transient // don't persist; not a column
+ private String passwordConfirm;
+
+ @ManyToMany
+ @ElementCollection
+ private List userRoles;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPasswordConfirm() {
+ return passwordConfirm;
+ }
+
+ public void setPasswordConfirm(String passwordConfirm) {
+ this.passwordConfirm = passwordConfirm;
+ }
+
+ public List getUserRoles() {
+ return userRoles;
+ }
+
+ public void setUserRoles(List userRoles) {
+ this.userRoles = userRoles;
+ }
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/model/UserProfileRole.java b/src/main/java/com/devos/perscholas/casestudy/model/UserProfileRole.java
new file mode 100644
index 000000000..4cdc1e658
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/model/UserProfileRole.java
@@ -0,0 +1,44 @@
+package com.devos.perscholas.casestudy.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Entity
+public class UserProfileRole {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String name;
+
+ @JsonIgnore
+ @ManyToMany
+ @ElementCollection
+ private List users;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getUsers() {
+ return users;
+ }
+
+ public void setUsers(List users) {
+ this.users = users;
+ }
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/repository/UserProfileRepository.java b/src/main/java/com/devos/perscholas/casestudy/repository/UserProfileRepository.java
new file mode 100644
index 000000000..95ae55b86
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/repository/UserProfileRepository.java
@@ -0,0 +1,10 @@
+package com.devos.perscholas.casestudy.repository;
+
+import com.devos.perscholas.casestudy.model.UserProfile;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserProfileRepository extends JpaRepository {
+ UserProfile findByUsername(String username);
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/repository/UserProfileRoleRepository.java b/src/main/java/com/devos/perscholas/casestudy/repository/UserProfileRoleRepository.java
new file mode 100644
index 000000000..9739c3b19
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/repository/UserProfileRoleRepository.java
@@ -0,0 +1,9 @@
+package com.devos.perscholas.casestudy.repository;
+
+import com.devos.perscholas.casestudy.model.UserProfileRole;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserProfileRoleRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/service/SecurityServiceImpl.java b/src/main/java/com/devos/perscholas/casestudy/service/SecurityServiceImpl.java
new file mode 100644
index 000000000..24d93c4a3
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/service/SecurityServiceImpl.java
@@ -0,0 +1,46 @@
+package com.devos.perscholas.casestudy.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SecurityServiceImpl {
+ private static final Logger logger = LoggerFactory.getLogger(SecurityServiceImpl.class);
+ private AuthenticationManager authenticationManager;
+
+ private UserDetailsService userDetailsService;
+
+ @Autowired
+ public SecurityServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService) {
+ this.authenticationManager = authenticationManager;
+ this.userDetailsService = userDetailsService;
+ }
+
+ public String findLoggedInUsername() {
+ Object userDetails = SecurityContextHolder.getContext().getAuthentication().getDetails();
+ if (userDetails instanceof UserDetails) {
+ return ((UserDetails) userDetails).getUsername();
+ }
+
+ return null;
+ }
+
+ public void autologin(String username, String password) {
+ UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
+
+ authenticationManager.authenticate(usernamePasswordAuthenticationToken);
+
+ if (usernamePasswordAuthenticationToken.isAuthenticated()) {
+ SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
+ logger.debug(String.format("Auto login %s successfully!", username));
+ }
+ }
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/service/UserProfileService.java b/src/main/java/com/devos/perscholas/casestudy/service/UserProfileService.java
new file mode 100644
index 000000000..62134347b
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/service/UserProfileService.java
@@ -0,0 +1,51 @@
+package com.devos.perscholas.casestudy.service;
+
+import com.devos.perscholas.casestudy.model.UserProfile;
+import com.devos.perscholas.casestudy.repository.UserProfileRepository;
+import com.devos.perscholas.casestudy.repository.UserProfileRoleRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Service
+public class UserProfileService implements UserDetailsService {
+ private UserProfileRepository userRepository;
+ private UserProfileRoleRepository roleRepository;
+ private BCryptPasswordEncoder bCryptPasswordEncoder;
+
+ @Autowired
+ public UserProfileService(UserProfileRepository userRepository, UserProfileRoleRepository roleRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
+ this.userRepository = userRepository;
+ this.roleRepository = roleRepository;
+ this.bCryptPasswordEncoder = bCryptPasswordEncoder;
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ UserProfile user = userRepository.findByUsername(username);
+ Set grantedAuthorities = new HashSet<>();
+ user.getUserRoles().forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())));
+ return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
+ }
+
+ public void save(UserProfile user) {
+ user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
+ user.setUserRoles(roleRepository.findAll());
+ userRepository.save(user);
+ }
+
+ public UserProfile findByUsername(String username) {
+ return userRepository.findByUsername(username);
+ }
+}
diff --git a/src/main/java/com/devos/perscholas/casestudy/validator/UserProfileValidator.java b/src/main/java/com/devos/perscholas/casestudy/validator/UserProfileValidator.java
new file mode 100644
index 000000000..edb62a663
--- /dev/null
+++ b/src/main/java/com/devos/perscholas/casestudy/validator/UserProfileValidator.java
@@ -0,0 +1,43 @@
+package com.devos.perscholas.casestudy.validator;
+
+
+import com.devos.perscholas.casestudy.model.UserProfile;
+import com.devos.perscholas.casestudy.service.UserProfileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+@Component
+public class UserProfileValidator implements Validator {
+ @Autowired
+ private UserProfileService userService;
+
+ @Override
+ public boolean supports(Class> aClass) {
+ return UserProfile.class.equals(aClass);
+ }
+
+ @Override
+ public void validate(Object o, Errors errors) {
+ UserProfile user = (UserProfile) o;
+
+ ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "NotEmpty");
+ if (user.getUsername().length() < 6 || user.getUsername().length() > 32) {
+ errors.rejectValue("username", "Size.userForm.username");
+ }
+ if (userService.findByUsername(user.getUsername()) != null) {
+ errors.rejectValue("username", "Duplicate.userForm.username");
+ }
+
+ ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty");
+ if (user.getPassword().length() < 8 || user.getPassword().length() > 32) {
+ errors.rejectValue("password", "Size.userForm.password");
+ }
+
+ if (!user.getPasswordConfirm().equals(user.getPassword())) {
+ errors.rejectValue("passwordConfirm", "Diff.userForm.passwordConfirm");
+ }
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 000000000..b1fbb772a
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://localhost:3306/casestudy
+spring.datasource.username=root
+spring.datasource.password=
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
+spring.datasource.initialize=true
+spring.jpa.show-sql=true
+
+spring.mvc.view.prefix: /WEB-INF/jsp/
+spring.mvc.view.suffix: .jsp
+spring.messages.basename=validation
\ No newline at end of file
diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css
new file mode 100644
index 000000000..98c2585c1
--- /dev/null
+++ b/src/main/resources/static/css/style.css
@@ -0,0 +1,38 @@
+@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap');
+
+html {
+ font-family: 'Lato', sans-serif;
+}
+
+.navbar {
+ background-color: rgba(0,0,0,.8)!important;
+}
+
+.card {
+ position: relative;
+ left: 50%;
+ transform: translate(-50%, 0);
+ width: 80%;
+ margin-top: 1em;
+}
+
+.card .img-fluid {
+ min-height: 250px;
+}
+
+.card .text-muted {
+ padding-left: 1em;
+}
+
+.img-thumbnail {
+ width: 180px;
+ height: 180px;
+}
+
+.dark {
+ background-color: #333333;
+}
+
+.light {
+ background-color: rgb(191, 191, 191);
+}
\ No newline at end of file
diff --git a/src/main/resources/static/img/stock-chart.jpg b/src/main/resources/static/img/stock-chart.jpg
new file mode 100644
index 000000000..c1482e06a
Binary files /dev/null and b/src/main/resources/static/img/stock-chart.jpg differ
diff --git a/src/main/resources/static/js/company.js b/src/main/resources/static/js/company.js
new file mode 100644
index 000000000..9635d8580
--- /dev/null
+++ b/src/main/resources/static/js/company.js
@@ -0,0 +1,127 @@
+// API Values
+let company = "amzn"
+
+// Make our API call using fetch
+function getCompanyData() {
+ const container = document.getElementById("container");
+ const template = document.getElementById("template");
+
+ const clone = template.content.cloneNode(true);
+ container.appendChild(clone);
+
+ //API call for logo
+ fetch("https://cloud.iexapis.com/stable/stock/" + company + "/logo?token=pk_fcd35c5ba2944133af0f4967d619ece3")
+ .then(response => response.json())
+ .then(data => {
+ // Object Keys and Values
+ let keyData = Object.keys(data)
+ let keyValues = Object.values(data)
+ for (let i = 0; i < keyData.length; i++) {
+ // Create and Append our elements to the company data id set in html file
+ document.getElementById("company-logo").src = keyValues[i];
+ }
+ })
+
+ //API call for company info
+ fetch("https://www.alphavantage.co/query?function=OVERVIEW&symbol=" + company + "&apikey=7IEE375T5KRNN35W")
+ .then(response => response.json())
+ .then(data => {
+ document.getElementById("ticker").innerHTML = data.Symbol;
+ document.getElementById("companyName").innerHTML = data.Name;
+ document.getElementById("assetType").innerHTML = data.AssetType;
+ document.getElementById("exchange").innerHTML = data.Exchange;
+ document.getElementById("currency").innerHTML = data.Currency;
+ document.getElementById("sector").innerHTML = data.Sector;
+ document.getElementById("industry").innerHTML = data.Industry;
+ document.getElementById("52weekHigh").innerHTML = data['52WeekHigh'];
+ document.getElementById("52weekLow").innerHTML = data['52WeekLow'];
+ document.getElementById("companyDescription").innerHTML = data.Description;
+ document.getElementById("address").innerHTML = data.Address;
+ document.getElementById("employees").innerHTML = data.FullTimeEmployees;
+ document.getElementById("fyEnd").innerHTML = data.FiscalYearEnd;
+ document.getElementById("quarter").innerHTML = data.LatestQuarter;
+ document.getElementById("marketCap").innerHTML = data.MarketCapitalization;
+ document.getElementById("revenue").innerHTML = data.RevenuePerShareTTM;
+ document.getElementById("grossProfit").innerHTML = data.GrossProfitTTM;
+ document.getElementById("profitMargin").innerHTML = data.ProfitMargin;
+ document.getElementById("peRatio").innerHTML = data.PERatio;
+ document.getElementById("pegRatio").innerHTML = data.PEGRatio;
+ document.getElementById("trailingPE").innerHTML = data.TrailingPE;
+ document.getElementById("forwardPE").innerHTML = data.ForwardPE;
+ document.getElementById("analystTarget").innerHTML = data.AnalystTargetPrice;
+ document.getElementById("divPerShare").innerHTML = data.DividendPerShare;
+ document.getElementById("divYield").innerHTML = data.DividendYield;
+ document.getElementById("divDate").innerHTML = data.DividendDate;
+ document.getElementById("exDivDate").innerHTML = data.ExDividendDate;
+ document.getElementById("forwardDivRate").innerHTML = data.ForwardAnnualDividendRate;
+ document.getElementById("forwardDivYield").innerHTML = data.ForwardAnnualDividendYield;
+ document.getElementById("payoutRatio").innerHTML = data.PayoutRatio;
+
+ })
+
+ let labelArray = [];
+ let dataArray = [];
+ //API call for chart information
+ fetch("https://cloud.iexapis.com/stable/stock/" + company + "/chart/5d?chartCloseOnly=true&token=pk_fcd35c5ba2944133af0f4967d619ece3")
+ .then(response => response.json())
+ .then(data => {
+ console.log(data)
+ for (i = 0; i < data.length; i++) {
+ dataArray[i] = data[i].close;
+ }
+ for (i = 0; i < data.length; i++) {
+ labelArray[i] = data[i].date;
+ }
+ }).then(
+ setTimeout(() => { //needed to create the template before trying to draw the chart
+ createChart(labelArray, dataArray);
+ }, 300)
+ )
+
+ //API call for company specific news
+ getNews(company);
+}
+
+async function changeCompanyData() {
+ deleteData().then(
+ getCompanyData()
+ )
+}
+
+async function deleteData() {
+ document.getElementById("container").innerHTML = "";
+ document.getElementById("newsContainer").innerHTML = "";
+ let text = document.getElementById("companyInput").value
+ company = text
+ return company
+}
+
+function createChart(labels, dataPoints) {
+ console.log(labels)
+ console.log(dataPoints)
+ var ctx = document.getElementById('myChart');
+ var myChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: labels,
+ datasets: [{
+ label: 'Historical Stock Price',
+ data: dataPoints,
+ fill: false,
+ lineTension: 0,
+ backgroundColor: 'rgba(0, 0, 0, 1)',
+ borderColor: 'rgba(0, 0, 0, 1)'
+
+ }]
+ },
+ options: {
+ scales: {
+ yAxes: [{
+ ticks: {
+ beginAtZero: false
+ }
+ }]
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/main/resources/static/js/utils.js b/src/main/resources/static/js/utils.js
new file mode 100644
index 000000000..8c4090d04
--- /dev/null
+++ b/src/main/resources/static/js/utils.js
@@ -0,0 +1,95 @@
+function showPassword() {
+ var pass = document.getElementById("inputPassword");
+ if (pass.type === "password") {
+ pass.type = "text";
+ } else {
+ pass.type = "password";
+ }
+}
+
+function getNews(company) {
+ var today = new Date();
+ var dd = String(today.getDate()).padStart(2, '0');
+ var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
+ var yyyy = today.getFullYear();
+
+ var dateTo = yyyy + '-' + mm + '-' + dd;
+ var dateFrom = "";
+ if (mm == 01) {
+ dateFrom = (yyyy - 1) + '-' + '12' + '-' + dd;
+ }
+ else {
+ dateFrom = yyyy + '-' + (mm - 1) + '-' + dd;
+ }
+
+ if (company == 'general') {
+ apiCall = 'https://finnhub.io/api/v1/news?category=general&token=btek0bn48v6qag0a1k40';
+ }
+ else {
+ apiCall = 'https://finnhub.io/api/v1/company-news?symbol=' + company + '&from=' + dateFrom + '&to=' + dateTo + '&token=btek0bn48v6qag0a1k40';
+ }
+
+ fetch(apiCall)
+ .then(response => response.json())
+ .then(data => {
+ // Object Keys and Values
+ let keyData = Object.keys(data)
+ let keyValues = Object.values(data)
+
+ for (let i = 0; i < keyData.length; i++) {
+ var title = keyValues[i].headline;
+ var summary = keyValues[i].summary;
+ var pic = keyValues[i].image;
+ var timeStamp = keyValues[i].datetime;
+ var newsUrl = keyValues[i].url;
+ var date = timeConverter(timeStamp);
+
+ var template = [
+ '
'
+ ].join("\n");
+
+ $("#newsContainer").append(template);
+
+ if (i == 20) { break };
+ }
+ })
+}
+
+function timeConverter(UNIX_timestamp) {
+ var a = new Date(UNIX_timestamp * 1000);
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ var year = a.getFullYear();
+ var month = months[a.getMonth()];
+ var date = a.getDate();
+ var hour = a.getHours();
+ var period = 'AM';
+ var min = a.getMinutes();
+ if (min < 10) {
+ min = '0' + min;
+ }
+ if (hour > 12) {
+ hour = hour - 12;
+ period = 'PM';
+ }
+ else if (hour == 12) {
+ period = 'PM';
+ }
+ else if (hour == 0) {
+ hour = 12;
+ }
+ var time = date + '-' + month + '-' + year + ' ' + hour + ':' + min + period;
+ return time;
+}
\ No newline at end of file
diff --git a/src/main/resources/validation.properties b/src/main/resources/validation.properties
new file mode 100644
index 000000000..9309bdf06
--- /dev/null
+++ b/src/main/resources/validation.properties
@@ -0,0 +1,5 @@
+NotEmpty=This field is required.
+Size.userForm.username=Username require between 6 and 32 characters.
+Duplicate.userForm.username=Someone is already using this username.
+Size.userForm.password=Passwords require at least 8 characters.
+Diff.userForm.passwordConfirm=These passwords do not match.
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/jsp/company-overview.jsp b/src/main/webapp/WEB-INF/jsp/company-overview.jsp
new file mode 100644
index 000000000..81417b8f5
--- /dev/null
+++ b/src/main/webapp/WEB-INF/jsp/company-overview.jsp
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+ Company
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IBM
+
+
+
Name: International Business Machines Corporation
+
Asset Type: Stock
+
Exchange: NYSE
+
Currency: USD
+
Sector: Technology
+
Industry: Information Technology Services
+
52 Week High: 158.75
+
52 Week Low: 90.56
+
+
+
+
+
+
+
+
+
+
+
+
Description
+
+ International Business Machines Corporation operates as an integrated solutions and services company
+ worldwide. Its Cloud & Cognitive Software segment offers software for vertical and domain-specific
+ solutions in health, financial services, and Internet of Things (IoT), weather, and security
+ software and services application areas; and customer information control system and storage, and
+ analytics and integration software solutions to support client mission critical on-premise workloads
+ in banking, airline, and retail industries. It also offers middleware and data platform software,
+ including Red Hat, which enables the operation of clients'
+ hybrid multi-cloud environments; and Cloud Paks, WebSphere distributed, and analytics platform
+ software, such as DB2 distributed, information integration, and enterprise content management, as
+ well as IoT, Blockchain and AI/Watson platforms. The company's
+ Global Business Services segment offers business consulting services; system integration,
+ application management, maintenance, and support services for packaged software; finance,
+ procurement, talent and engagement, industry-specific business process outsourcing services; IT
+ infrastructure and platform services. Its Global Technology Services segment provides project,
+ managed, outsourcing, and cloud-delivered services for enterprise IT infrastructure environments;
+ and IT infrastructure support services. It's
+ Systems segment offers servers for businesses, cloud service providers, and scientific computing
+ organizations; data storage products and solutions; and z/OS, an enterprise operating system, as
+ well as Linux. Its Global Financing segment provides lease, installment payment, loan financing,
+ short-term working capital financing, and remanufacturing and remarketing services. It was formerly
+ known as Computing-Tabulating-Recording Co. and changed its name to International Business Machines
+ Corporation in 1924. It was founded in 1911 and is headquartered in Armonk, New York.
+
+
+
+
+
+
+
General Info
+
+
Address: One New Orchard Road, Armonk, NY, United States, 10504
+
Hello, and welcome to Nick's Stock Market Case Study application.
+
To get started, please select 'Market News' or 'Company Overview' in the navbar at the top.
+
Market News will show you 20 of the most recent news articles about publicly traded companies. Company
+ Overview will allow you to enter a symbol into the search bar see information related to that company.
+
When you are finished exploring company information, click 'Logout' on the far right side of the navbar.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/com/devos/perscholas/casestudy/CasestudyApplicationTests.java b/src/test/java/com/devos/perscholas/casestudy/CasestudyApplicationTests.java
new file mode 100644
index 000000000..df228fbaa
--- /dev/null
+++ b/src/test/java/com/devos/perscholas/casestudy/CasestudyApplicationTests.java
@@ -0,0 +1,13 @@
+package com.devos.perscholas.casestudy;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CasestudyApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}