Skip to content
Permalink
Browse files

Business logic added. Frontend pages created. (It's the starting poin…

…t for securing the app).
  • Loading branch information
little-pinecone committed Jan 4, 2019
1 parent 5183aa4 commit ea11fb6a53b3b1575673669d76fd0bb2f65f4c0e
Showing with 554 additions and 37 deletions.
  1. +57 −0 README.md
  2. +18 −0 ...end/src/main/java/in/keepgrowing/jwtspringbootangularscaffolding/config/DevCorsConfiguration.java
  3. +29 −0 backend/src/main/java/in/keepgrowing/jwtspringbootangularscaffolding/config/MvcConfiguration.java
  4. +36 −0 backend/src/main/java/in/keepgrowing/jwtspringbootangularscaffolding/cookie/Cookie.java
  5. +19 −0 backend/src/main/java/in/keepgrowing/jwtspringbootangularscaffolding/cookie/CookieController.java
  6. +1 −0 backend/src/main/resources/application.properties
  7. +32 −0 ...end/src/test/java/in/keepgrowing/jwtspringbootangularscaffolding/cookie/CookieControllerTest.java
  8. +3 −2 frontend/src/main/angular/angular.json
  9. +5 −0 frontend/src/main/angular/package-lock.json
  10. +1 −0 frontend/src/main/angular/package.json
  11. +6 −1 frontend/src/main/angular/src/app/app-routing.module.ts
  12. +4 −20 frontend/src/main/angular/src/app/app.component.html
  13. +10 −9 frontend/src/main/angular/src/app/app.component.spec.ts
  14. +1 −1 frontend/src/main/angular/src/app/app.component.ts
  15. +14 −2 frontend/src/main/angular/src/app/app.module.ts
  16. +31 −0 frontend/src/main/angular/src/app/auth/components/login/login.component.html
  17. +4 −0 frontend/src/main/angular/src/app/auth/components/login/login.component.scss
  18. +33 −0 frontend/src/main/angular/src/app/auth/components/login/login.component.spec.ts
  19. +15 −0 frontend/src/main/angular/src/app/auth/components/login/login.component.ts
  20. +44 −0 frontend/src/main/angular/src/app/cookies/cookie-data.service.spec.ts
  21. +25 −0 frontend/src/main/angular/src/app/cookies/cookie-data.service.ts
  22. +3 −0 frontend/src/main/angular/src/app/cookies/cookie.ts
  23. +4 −0 frontend/src/main/angular/src/app/layout/footer/footer.component.html
  24. 0 frontend/src/main/angular/src/app/layout/footer/footer.component.scss
  25. +25 −0 frontend/src/main/angular/src/app/layout/footer/footer.component.spec.ts
  26. +16 −0 frontend/src/main/angular/src/app/layout/footer/footer.component.ts
  27. +3 −0 frontend/src/main/angular/src/app/layout/header/header.component.html
  28. 0 frontend/src/main/angular/src/app/layout/header/header.component.scss
  29. +25 −0 frontend/src/main/angular/src/app/layout/header/header.component.spec.ts
  30. +15 −0 frontend/src/main/angular/src/app/layout/header/header.component.ts
  31. +13 −0 frontend/src/main/angular/src/app/pages/cookie-list/cookie-list.component.html
  32. +3 −0 frontend/src/main/angular/src/app/pages/cookie-list/cookie-list.component.scss
  33. +31 −0 frontend/src/main/angular/src/app/pages/cookie-list/cookie-list.component.spec.ts
  34. +24 −0 frontend/src/main/angular/src/app/pages/cookie-list/cookie-list.component.ts
  35. +2 −1 frontend/src/main/angular/src/environments/environment.ts
  36. BIN frontend/src/main/angular/src/favicon.ico
  37. BIN frontend/src/main/angular/src/favicon.png
  38. +2 −1 frontend/src/main/angular/src/index.html
  39. BIN readme-images/cookie-dispenser-screenshot.png
  40. BIN readme-images/login-page-screenshot.png
  41. BIN readme-images/logo_250x60.png
@@ -0,0 +1,57 @@
# jwt-spring-boot-angular-scaffolding

![keep growing logo](readme-images/logo_250x60.png)

This project is a multi-module application, using Spring Boot for the backend and Angular for the frontend. The project can be built into a single jar file using Maven. You can also run the modules separately during development.
To learn how to set up a project like this one, check out the [Integrate Angular with a Spring Boot project](http://keepgrowing.in/java/springboot/integrate-angular-with-a-spring-boot-project/) post.

It's ready to be secured with JSON Web Token.

## Getting Started

To clone the repository, run in the command line:
```bash
$ git clone https://github.com/little-pinecone/jwt-spring-boot-angular-scaffolding.git
```

You can build the application with:
```bash
$ mvn clean install
```

## Built With

* Java 11
* [Spring Boot 2.1.1](https://start.spring.io/)
* [Angular 7](https://angular.io/)
* [Bootstrap 4](https://getbootstrap.com/)
* [Maven](https://maven.apache.org/)
* [frontend-maven-plugin](https://github.com/eirslett/frontend-maven-plugin)

## Overview and technical features

The project currently serves a dummy login page and hard-coded pastry data returned from the API.

## Running tests

Run all backend tests with the following command in the root directory:
```bash
$ mvn test
```
Run all frontend tests with the following command in the `frontend/src/main/angular` directory:
```bash
$ ng test
```

## Screenshots

![login page](readme-images/login-page-screenshot.png)
![cookies](readme-images/cookie-dispenser-screenshot.png)

## To do

* Authorisation and authentication - securing backend and frontend layers, handling logging in and logging out.

## License

This project is licensed under the MIT License - see the [license details](https://opensource.org/licenses/MIT).
@@ -0,0 +1,18 @@
package in.keepgrowing.jwtspringbootangularscaffolding.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@Profile("development")
public class DevCorsConfiguration implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.exposedHeaders("Authorization");
}
}
@@ -0,0 +1,29 @@
package in.keepgrowing.jwtspringbootangularscaffolding.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;

import java.io.IOException;

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);

return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
: new ClassPathResource("/static/index.html");
}
});
}
}
@@ -0,0 +1,36 @@
package in.keepgrowing.jwtspringbootangularscaffolding.cookie;

import java.util.Objects;

public class Cookie {

private String flavour;

public Cookie(String flavour) {
this.flavour = flavour;
}

public String getFlavour() {
return flavour;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cookie cookie = (Cookie) o;
return Objects.equals(flavour, cookie.flavour);
}

@Override
public int hashCode() {
return Objects.hash(flavour);
}

@Override
public String toString() {
return "Cookie{" +
"flavour='" + flavour + '\'' +
'}';
}
}
@@ -0,0 +1,19 @@
package in.keepgrowing.jwtspringbootangularscaffolding.cookie;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("api/cookies")
public class CookieController {

@GetMapping
public List<Cookie> getCookies() {
return Arrays.asList(new Cookie("chocolate"), new Cookie("vanilla"),
new Cookie("cinnamon"), new Cookie("coconut"));
}
}
@@ -0,0 +1 @@
spring.profiles.active=development
@@ -0,0 +1,32 @@
package in.keepgrowing.jwtspringbootangularscaffolding.cookie;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(value = CookieController.class)
public class CookieControllerTest {

@Autowired
private MockMvc mvc;

@Test
public void getsCookies() throws Exception {
mvc.perform(get("/api/cookies")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].flavour", is("chocolate")))
.andExpect(jsonPath("$.[1].flavour", is("vanilla")))
.andExpect(status().isOk());
}

}
@@ -23,11 +23,12 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/favicon.png",
"src/assets"
],
"styles": [
"src/styles.scss"
"src/styles.scss",
"node_modules/bootstrap/scss/bootstrap.scss"
],
"scripts": []
},

Some generated files are not rendered by default. Learn more.

@@ -19,6 +19,7 @@
"@angular/platform-browser": "~7.1.0",
"@angular/platform-browser-dynamic": "~7.1.0",
"@angular/router": "~7.1.0",
"bootstrap": "^4.2.1",
"core-js": "^2.5.4",
"rxjs": "~6.3.3",
"tslib": "^1.9.0",
@@ -1,7 +1,12 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './auth/components/login/login.component';
import { CookieListComponent } from './pages/cookie-list/cookie-list.component';

const routes: Routes = [];
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'cookies', component: CookieListComponent }
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
@@ -1,21 +1,5 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
<app-header></app-header>
<div class="container mt-5">
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>

<router-outlet></router-outlet>
@@ -1,6 +1,12 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { Component } from '@angular/core';

@Component({selector: 'app-header', template: ''})
class HeaderComponent {}
@Component({selector: 'app-footer', template: ''})
class FooterComponent {}

describe('AppComponent', () => {
beforeEach(async(() => {
@@ -9,7 +15,9 @@ describe('AppComponent', () => {
RouterTestingModule
],
declarations: [
AppComponent
AppComponent,
HeaderComponent,
FooterComponent
],
}).compileComponents();
}));
@@ -23,13 +31,6 @@ describe('AppComponent', () => {
it(`should have as title 'angular'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('angular');
});

it('should render title in a h1 tag', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular!');
expect(app.title).toEqual('Cookie dispenser');
});
});
@@ -6,5 +6,5 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'angular';
title = 'Cookie dispenser';
}
@@ -3,14 +3,26 @@ import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CookieListComponent } from './pages/cookie-list/cookie-list.component';
import { LoginComponent } from './auth/components/login/login.component';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { FooterComponent } from './layout/footer/footer.component';
import { HeaderComponent } from './layout/header/header.component';

@NgModule({
declarations: [
AppComponent
AppComponent,
CookieListComponent,
LoginComponent,
FooterComponent,
HeaderComponent
],
imports: [
BrowserModule,
AppRoutingModule
AppRoutingModule,
HttpClientModule,
FormsModule,
],
providers: [],
bootstrap: [AppComponent]
@@ -0,0 +1,31 @@
<div class="mt-5">
<section>
<h2 class="h1 py-5 font-weight-bold text-center">Login</h2>
<div class="d-flex justify-content-center">
<div class="card login-form text-white bg-info">
<div class="card-header">
<h5 class="text-center">Provide your credentials to see the cookies</h5>
</div>
<div class="card-body">
<form #loginForm="ngForm">
<div class="form-group">
<label for="username">Username:</label>
<input id="username" class="form-control" type="text"
placeholder="Username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input id="password" class="form-control" type="password"
placeholder="Password" name="password" required>
</div>
<div class="text-center">
<button class="btn btn-warning">
<span class="ml-1">Log in</span>
</button>
</div>
</form>
</div>
</div>
</div>
</section>
</div>
@@ -0,0 +1,4 @@
.login-form {
max-width: 500px;
min-width: 500px;
}

0 comments on commit ea11fb6

Please sign in to comment.
You can’t perform that action at this time.