diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6161bf863 --- /dev/null +++ b/.gitignore @@ -0,0 +1,562 @@ + +# Created by https://www.gitignore.io/api/eclipse,intellij,netbeans,notepadpp,sublimetext,visualstudio +# Edit at https://www.gitignore.io/?templates=eclipse,intellij,netbeans,notepadpp,sublimetext,visualstudio + +### Eclipse ### +.metadata +bin/ +tmp/ +*.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/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +target +target/ +target/* +*.iml +.idea +.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/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 +.idea/**/sonarlint/ + +# SonarQube Plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator/ + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### NotepadPP ### +# Notepad++ backups # + +### 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 + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# End of https://www.gitignore.io/api/eclipse,intellij,netbeans,notepadpp,sublimetext,visualstudio diff --git a/README-Sample.md b/README-Sample.md new file mode 100644 index 000000000..a6e1dc3f7 --- /dev/null +++ b/README-Sample.md @@ -0,0 +1,74 @@ +# Project Title + +* **Objective** - To create a product... +* **Purpose** - To gain familiarity the following features... + + + + +## Instructions + + +### Testing Application via Postman + +* Ensure that the `start-class` tag in your `pom.xml` encapsulates `com.github.curriculeon.MyApplication` +* Open a command line and navigate to the project's root directory and run this command: + * `mvn spring-boot:run` +* Launch the [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en) app and enter the URI `http://localhost:8080/` and hit Send. +* If your application cannot run because something is occupying a port, use this command with the respective port number specified: + * **OSX and Linux** + * ``kill -kill `lsof -t -i tcp:8080` `` + * **Windows** + * _For use in command line_: + * `for /f "tokens=5" %a in ('netstat -aon ^| find ":8080" ^| find "LISTENING"') do taskkill /f /pid %a` + * _For use in bat-file_: + * `for /f "tokens=5" %%a in ('netstat -aon ^| find ":8080" ^| find "LISTENING"') do taskkill /f /pid %%a` + + + + +## How to Download + +#### Part 1 - Forking the Project +* To _fork_ the project, click the `Fork` button located at the top right of the project. + + +#### Part 2 - Navigating to _forked_ Repository +* Navigate to your github profile to find the _newly forked repository_. +* Copy the URL of the project to the clipboard. + +#### Part 3 - Cloning _forked_ repository +* Clone the repository from **your account** into the `~/dev` directory. + * if you do not have a `~/dev` directory, make one by executing the following command: + * `mkdir ~/dev` + * navigate to the `~/dev` directory by executing the following command: + * `cd ~/dev` + * clone the project by executing the following command: + * `git clone https://github.com/MYUSERNAME/NAMEOFPROJECT` + +#### Part 4 - Check Build +* Ensure that the tests run upon opening the project. + * You should see `Tests Failed: 99 of 99 tests` + + + + + + + +## How to Submit + +#### Part 1 - _Pushing_ local changes to remote repository +* from a _terminal_ navigate to the root directory of the _cloned_ project. +* from the root directory of the project, execute the following commands: + * add all changes + * `git add .` + * commit changes to be pushed + * `git commit -m 'I have added changes'` + * push changes to your repository + * `git push -u origin master` + +#### Part 2 - Submitting assignment +* from the browser, navigate to the _forked_ project from **your** github account. +* click the `Pull Requests` tab. +* select `New Pull Request` \ No newline at end of file diff --git a/README-annotations.md b/README-annotations.md new file mode 100644 index 000000000..5bcd496a0 --- /dev/null +++ b/README-annotations.md @@ -0,0 +1,171 @@ +### About Spring Annotations + +#### `@Entity` +* Annotates class signature +* **Description:** + * Allows the persistence provider to recognize it as a persistence class. + * An object representative of a snap shot of data from a database. + * By default, maps this entity to a table whose name is the name of the annotated class. Can be rerouted via the `@Table` annotation + * Entities are said to be _fungible_, or _mutually interchangeable_. +* **Pre-requesites for use:** + * An interface cannot be an entity. + * An enum cannot be an entity. + * The class can be abstract or concrete. + * The class must define a no-arg constructor. + * Each `Entity` must be annotated with a respective `ID`. + + + + + + + + +
+ +#### `@Id` +* Annotates field declarations +* **Description:** + * Denotes the primary key for this `Entity`. + * Can be generated manually by application or by automatically by the persistence provider. +* **Pre-requisites for use:** + * Class must be annotated with `@Entity` + + + + + + + + + +
+ +#### `@GeneratedValue(strategy = GenerationType.ENUM_VALUE)` +* Annotates `Id` fields. +* **Description:** + * Specifies how the persistence provider will generate this value. + * `GenerationType.SEQUENCE` - specifies the use of database SQL sequence + * `GenerationType.IDENTITY` - uses a database identity column + * `GenerationType.TABLE` - instructs provider to store the sequence name and its current value in a table, increasing the value of each time a new instance of the entity is persisted. + * `GenerationType.AUTO` - default when nothing specified. Provider does generation of a key automatically. It will select an appropriate strategy for a particular database. +* **Pre-requesites for use:** + * Field must be annotated with `@Id`. + + + + + + + + + + + +
+ +#### `@Autowired` +* Annotates field declaration or method-parameters +* **Description** + * injects bean by type + * can be used alone. + * If is used alone, it will be wired by type + * If more than one bean of same type are declared in the container `@Autowired` does not know which beans to use for injection. +* **Pre-requesites for use:** + * Field-type must be annotated with some form of `@Component`. + + + + + + + +
+ +#### `@Component` +* Annotates class signature +* **Description** + * denotes that Spring framework will autodetect these classes for dependency injection when annotation-based configuration and classpath scanning is used. +* **Prerequisites for use:** + * none + + + + + + +
+ +#### `@Service` +* Annotates class signature +* **Description** + * specialized form of `@Component` + * responsible for performing service tasks + * in many case you use this annotation for best practice, but isn't _always_ necessary. +* **Prerequisites for use:** + * none + + + + +
+ +#### `@Controller` +* Annotates class signature +* **Description** + * specialized form of `@Component` + * indicates that a particular class serves the role of a controller + * acts as a stereotype for the annotated class, indicating its role + * dispatcher scans such annotated classes for mapped methods and detects @RequestMapping annotations +* **Pre-requesites for use:** + * none + + + + + + + + +
+ +#### `@RequestMapping` +* Annotates a method signature +* **Description** + * annotation maps HTTP requests to handler methods of MVC and REST controllers. +* **Pre-requesites for use** + * class must be a annotated with `@Controller` + + + + + + + + +
+ +#### `@PathVariable` +* Annotates a method parameter +* **Description** + * indicates that a method parameter should be bound to a URI template variable +* **Pre-requesites for use** + * class must be a annotated with `@Controller` + + + + + + + +
+ +#### `@RequestParam` +* Annotates a method parameter +* **Description** + * indicates that a method parameter should be bound to a web request parameter + * used to extract query parameters, form parameters +* **Pre-requesites for use** + * class must be a annotated with `@Controller` + + \ No newline at end of file diff --git a/README.md b/README.md index 8cdfa1bdd..203acfdbc 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,8 @@ # Full Stack Web Application -* **Objective** - to create an implementation of a web service -* **Purpose** - to demonstrate the construction of a full-stacked web-application +* **Project** - Online banking website * **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) + * This was created to show my project creation skills following my PerScholas graduation. It is my final project and display skills I learned! + * To download make sure you hve your favorite IDE, and a command line capable of cloning github projects. A gif guide is given below! - - -## 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. - - - - -## Developmental Notes - -### 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 - - * **Frontend** - 1. Java Server Pages - - * **Business Logic** - 1. Java - 2. TypeScript - - * **WebServer Implementation** - 1. Spring Boot - 2. At least 1 [backing service](https://12factor.net/backing-services) API - - * **Data Layer** - 1. MySQL - 2. PostgreSQL - 3. MariaDB - - * **Web Server Cloud Deployment** - 1. Heroku - 2. AWS EC2 Instance - - * **Web Application Cloud Deployment** - 1. Netlify - 2. AWS EC2 Instance - - - - -### 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/). +![](screen-capture.gif) diff --git a/how-to-use.gif b/how-to-use.gif new file mode 100644 index 000000000..83c8a1181 Binary files /dev/null and b/how-to-use.gif 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..2198716e0 --- /dev/null +++ b/kill-8080.sh @@ -0,0 +1 @@ +kill -kill `lsof -t -i tcp:8080` diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..09e45d803 --- /dev/null +++ b/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + SpringBootSampleWebApp + 0.0.1-SNAPSHOT + war + Curriculeon SpringBoot JSP Login Page + Demo project for Spring Boot With Login JSP View + + org.springframework.boot + spring-boot-starter-parent + 2.1.2.RELEASE + + + + UTF-8 + UTF-8 + 1.8 + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.data + spring-data-jpa + + + javax.servlet + jstl + + + + org.apache.tomcat.embed + tomcat-embed-jasper + + + + org.eclipse.jdt.core.compiler + ecj + 4.6.1 + + + junit + junit + 4.12 + test + + + + com.h2database + h2 + runtime + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/screen-capture.gif b/screen-capture.gif new file mode 100644 index 000000000..ce47eea33 Binary files /dev/null and b/screen-capture.gif differ diff --git a/src/main/java/com/github/curriculeon/MainApplication.java b/src/main/java/com/github/curriculeon/MainApplication.java new file mode 100644 index 000000000..93877b366 --- /dev/null +++ b/src/main/java/com/github/curriculeon/MainApplication.java @@ -0,0 +1,11 @@ +package com.github.curriculeon; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MainApplication { + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } +} diff --git a/src/main/java/com/github/curriculeon/MyObject.java b/src/main/java/com/github/curriculeon/MyObject.java new file mode 100644 index 000000000..2301f17e8 --- /dev/null +++ b/src/main/java/com/github/curriculeon/MyObject.java @@ -0,0 +1,7 @@ +package com.github.curriculeon; + +public class MyObject implements Runnable { + public void run() { + // TODO + } +} diff --git a/src/main/java/com/github/curriculeon/config/WebSecurityConfig.java b/src/main/java/com/github/curriculeon/config/WebSecurityConfig.java new file mode 100644 index 000000000..00e05a669 --- /dev/null +++ b/src/main/java/com/github/curriculeon/config/WebSecurityConfig.java @@ -0,0 +1,56 @@ +package com.github.curriculeon.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", "/h2-console/**").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .logout() + .permitAll(); + + http.csrf().disable(); + http.headers().frameOptions().disable(); + } + + @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/github/curriculeon/controller/UserAccountController.java b/src/main/java/com/github/curriculeon/controller/UserAccountController.java new file mode 100644 index 000000000..9676cd264 --- /dev/null +++ b/src/main/java/com/github/curriculeon/controller/UserAccountController.java @@ -0,0 +1,7 @@ +package com.github.curriculeon.controller; + +import org.springframework.beans.factory.annotation.Autowired; + +public class UserAccountController { + +} diff --git a/src/main/java/com/github/curriculeon/controller/UserProfileController.java b/src/main/java/com/github/curriculeon/controller/UserProfileController.java new file mode 100644 index 000000000..8d69d195a --- /dev/null +++ b/src/main/java/com/github/curriculeon/controller/UserProfileController.java @@ -0,0 +1,63 @@ +package com.github.curriculeon.controller; + +import com.github.curriculeon.model.UserProfile; +import com.github.curriculeon.service.SecurityServiceImpl; +import com.github.curriculeon.service.UserProfileService; +import com.github.curriculeon.validator.UserProfileValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; + +@Controller +@RequestMapping +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/github/curriculeon/model/UserAccount.java b/src/main/java/com/github/curriculeon/model/UserAccount.java new file mode 100644 index 000000000..993986334 --- /dev/null +++ b/src/main/java/com/github/curriculeon/model/UserAccount.java @@ -0,0 +1,55 @@ +package com.github.curriculeon.model; + +import org.springframework.lang.Nullable; + +import javax.persistence.*; +import java.util.Date; + +@Entity +public class UserAccount { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private Long balance; + + @Nullable + private Date nextPayment; + + @OneToOne + @PrimaryKeyJoinColumn(name = "user_id") + private UserProfile userId; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getBalance() { + return balance; + } + + public void setBalance(Long balance) { + this.balance = balance; + } + + @Nullable + public Date getNextPayment() { + return nextPayment; + } + + public void setNextPayment(@Nullable Date nextPayment) { + this.nextPayment = nextPayment; + } + + public UserProfile getUserId() { + return userId; + } + + public void setUserid(UserProfile userId) { + this.userId = userId; + } +} diff --git a/src/main/java/com/github/curriculeon/model/UserProfile.java b/src/main/java/com/github/curriculeon/model/UserProfile.java new file mode 100644 index 000000000..a9775fc31 --- /dev/null +++ b/src/main/java/com/github/curriculeon/model/UserProfile.java @@ -0,0 +1,64 @@ +package com.github.curriculeon.model; + +import javax.persistence.*; +import java.util.List; + +@Entity +public class UserProfile { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "user_id") + 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/github/curriculeon/model/UserProfileRole.java b/src/main/java/com/github/curriculeon/model/UserProfileRole.java new file mode 100644 index 000000000..2838c81d7 --- /dev/null +++ b/src/main/java/com/github/curriculeon/model/UserProfileRole.java @@ -0,0 +1,44 @@ +package com.github.curriculeon.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/github/curriculeon/repository/UserAccountRepository.java b/src/main/java/com/github/curriculeon/repository/UserAccountRepository.java new file mode 100644 index 000000000..481a7da6a --- /dev/null +++ b/src/main/java/com/github/curriculeon/repository/UserAccountRepository.java @@ -0,0 +1,7 @@ +package com.github.curriculeon.repository; + +import com.github.curriculeon.model.UserAccount; +import org.springframework.data.repository.CrudRepository; + +public interface UserAccountRepository extends CrudRepository { +} diff --git a/src/main/java/com/github/curriculeon/repository/UserProfileRepository.java b/src/main/java/com/github/curriculeon/repository/UserProfileRepository.java new file mode 100644 index 000000000..ae6bc3828 --- /dev/null +++ b/src/main/java/com/github/curriculeon/repository/UserProfileRepository.java @@ -0,0 +1,8 @@ +package com.github.curriculeon.repository; + +import com.github.curriculeon.model.UserProfile; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserProfileRepository extends JpaRepository { + UserProfile findByUsername(String username); +} diff --git a/src/main/java/com/github/curriculeon/repository/UserProfileRoleRepository.java b/src/main/java/com/github/curriculeon/repository/UserProfileRoleRepository.java new file mode 100644 index 000000000..552c67eb3 --- /dev/null +++ b/src/main/java/com/github/curriculeon/repository/UserProfileRoleRepository.java @@ -0,0 +1,8 @@ +package com.github.curriculeon.repository; + + +import com.github.curriculeon.model.UserProfileRole; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserProfileRoleRepository extends JpaRepository { +} diff --git a/src/main/java/com/github/curriculeon/service/SecurityServiceImpl.java b/src/main/java/com/github/curriculeon/service/SecurityServiceImpl.java new file mode 100644 index 000000000..fbe42fd48 --- /dev/null +++ b/src/main/java/com/github/curriculeon/service/SecurityServiceImpl.java @@ -0,0 +1,46 @@ +package com.github.curriculeon.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/github/curriculeon/service/UserAccountService.java b/src/main/java/com/github/curriculeon/service/UserAccountService.java new file mode 100644 index 000000000..6b49d40f1 --- /dev/null +++ b/src/main/java/com/github/curriculeon/service/UserAccountService.java @@ -0,0 +1,14 @@ +package com.github.curriculeon.service; + +import com.github.curriculeon.repository.UserAccountRepository; +import org.springframework.beans.factory.annotation.Autowired; + +public class UserAccountService { + private UserAccountRepository repository; + + @Autowired + public UserAccountService(UserAccountRepository repository) { + this.repository = repository; + } + +} diff --git a/src/main/java/com/github/curriculeon/service/UserProfileService.java b/src/main/java/com/github/curriculeon/service/UserProfileService.java new file mode 100644 index 000000000..e278f01fa --- /dev/null +++ b/src/main/java/com/github/curriculeon/service/UserProfileService.java @@ -0,0 +1,51 @@ +package com.github.curriculeon.service; + +import com.github.curriculeon.model.UserProfile; +import com.github.curriculeon.repository.UserProfileRoleRepository; +import com.github.curriculeon.repository.UserProfileRepository; +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/github/curriculeon/validator/UserProfileValidator.java b/src/main/java/com/github/curriculeon/validator/UserProfileValidator.java new file mode 100644 index 000000000..50b95b417 --- /dev/null +++ b/src/main/java/com/github/curriculeon/validator/UserProfileValidator.java @@ -0,0 +1,42 @@ +package com.github.curriculeon.validator; + +import com.github.curriculeon.model.UserProfile; +import com.github.curriculeon.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..bd222ec98 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,11 @@ +spring.mvc.view.prefix= / +spring.mvc.view.suffix= .jsp +spring.messages.basename=validation + +server.port=8080 +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console +spring.datasource.url=jdbc:h2:mem:testdb + +spring.datasource.username=root +spring.datasource.password=root \ 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/footer.jsp b/src/main/webapp/footer.jsp new file mode 100644 index 000000000..9cbb80181 --- /dev/null +++ b/src/main/webapp/footer.jsp @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/webapp/head_common.jsp b/src/main/webapp/head_common.jsp new file mode 100644 index 000000000..196e10bcc --- /dev/null +++ b/src/main/webapp/head_common.jsp @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/head_files.jsp b/src/main/webapp/head_files.jsp new file mode 100644 index 000000000..9c9626c3c --- /dev/null +++ b/src/main/webapp/head_files.jsp @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/login.jsp b/src/main/webapp/login.jsp new file mode 100644 index 000000000..ccce5f0c8 --- /dev/null +++ b/src/main/webapp/login.jsp @@ -0,0 +1,35 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> + + + + + + Log in with your account + + + +
+
+
+ + + +
+
+
+ diff --git a/src/main/webapp/registration.jsp b/src/main/webapp/registration.jsp new file mode 100644 index 000000000..a4e886276 --- /dev/null +++ b/src/main/webapp/registration.jsp @@ -0,0 +1,46 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> + + + + + + Create an account + + + +
+
+
+ + + +
+
+
+ diff --git a/src/main/webapp/resources/css/style.css b/src/main/webapp/resources/css/style.css new file mode 100644 index 000000000..a7b884abb --- /dev/null +++ b/src/main/webapp/resources/css/style.css @@ -0,0 +1,47 @@ +body { + font-family: 'Montserrat', "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 15px; + padding-top: 50px; + padding-bottom: 50px; + background-color: #efefef; + color: #2b2b2b; +} +.form-heading { + text-align: center; + text-transform: uppercase; +} +.form-signin { + max-width: 360px; + padding: 15px; + margin: 0 auto; +} +.form-signin .form-control { + position: relative; + height: auto; + padding: 10px; + font-size: 15px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.form-signin .form-control:focus { + z-index: 3; +} +.form-signin input, .form-signin button { + margin-top: 10px; +} +.form-signin .form-signin-heading, .form-signin .checkbox { + margin-bottom: 10px; +} +.form-signin .checkbox { + font-weight: normal; +} +.has-error { + color: #ff0000; + line-height: 1; + font-size: 14px; +} + +.paragraph { + border: solid black 2.5px +} \ No newline at end of file diff --git a/src/main/webapp/welcome.jsp b/src/main/webapp/welcome.jsp new file mode 100644 index 000000000..a693564ec --- /dev/null +++ b/src/main/webapp/welcome.jsp @@ -0,0 +1,53 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + Welcome! + + + +
+
+
+ + +
+ +
+ +

Welcome ${pageContext.request.userPrincipal.name}

+ +
+ +
+
+
+ + + +
+
+

+ placeholder +

+
+
+

+ placeholder +

+
+
+ diff --git a/src/test/java/com/github/curriculeon/MyObjectTest.java b/src/test/java/com/github/curriculeon/MyObjectTest.java new file mode 100644 index 000000000..2eac71dd4 --- /dev/null +++ b/src/test/java/com/github/curriculeon/MyObjectTest.java @@ -0,0 +1,18 @@ +package com.github.curriculeon; + +import org.junit.Assert; +import org.junit.Test; + +public class MyObjectTest { + @Test + public void testRun() { // TODO + // Given + MyObject myObject = new MyObject(); + + // when + myObject.run(); + + //then + Assert.assertNotNull(myObject.toString()); + } +}