Browse Source

Ionic, Spring Boot, and Stormpath. It's Bootiful! 🍻

Matt Raible 7 years ago
commit
56a1fba2da
82 changed files with 4664 additions and 0 deletions
  1. 17
    0
      client/.editorconfig
  2. 34
    0
      client/.gitignore
  3. 40
    0
      client/config.xml
  4. 6
    0
      client/ionic.config.json
  5. 52
    0
      client/package.json
  6. BIN
      client/resources/android/splash/drawable-land-hdpi-screen.png
  7. BIN
      client/resources/android/splash/drawable-land-ldpi-screen.png
  8. BIN
      client/resources/android/splash/drawable-land-mdpi-screen.png
  9. BIN
      client/resources/android/splash/drawable-land-xhdpi-screen.png
  10. BIN
      client/resources/android/splash/drawable-land-xxhdpi-screen.png
  11. BIN
      client/resources/android/splash/drawable-land-xxxhdpi-screen.png
  12. BIN
      client/resources/android/splash/drawable-port-hdpi-screen.png
  13. BIN
      client/resources/android/splash/drawable-port-ldpi-screen.png
  14. BIN
      client/resources/android/splash/drawable-port-mdpi-screen.png
  15. BIN
      client/resources/android/splash/drawable-port-xhdpi-screen.png
  16. BIN
      client/resources/android/splash/drawable-port-xxhdpi-screen.png
  17. BIN
      client/resources/android/splash/drawable-port-xxxhdpi-screen.png
  18. BIN
      client/resources/icon.png
  19. BIN
      client/resources/ios/splash/Default-568h@2x~iphone.png
  20. BIN
      client/resources/ios/splash/Default-667h.png
  21. BIN
      client/resources/ios/splash/Default-736h.png
  22. BIN
      client/resources/ios/splash/Default-Landscape-736h.png
  23. BIN
      client/resources/ios/splash/Default-Landscape@2x~ipad.png
  24. BIN
      client/resources/ios/splash/Default-Landscape~ipad.png
  25. BIN
      client/resources/ios/splash/Default-Portrait@2x~ipad.png
  26. BIN
      client/resources/ios/splash/Default-Portrait~ipad.png
  27. BIN
      client/resources/ios/splash/Default@2x~iphone.png
  28. BIN
      client/resources/ios/splash/Default~iphone.png
  29. BIN
      client/resources/splash.png
  30. 30
    0
      client/src/app/app.component.ts
  31. 1
    0
      client/src/app/app.html
  32. 60
    0
      client/src/app/app.module.ts
  33. 34
    0
      client/src/app/app.scss
  34. 5
    0
      client/src/app/main.ts
  35. BIN
      client/src/assets/imgs/logo.png
  36. BIN
      client/src/assets/imgs/stormpath-sdks.jpg
  37. 14
    0
      client/src/declarations.d.ts
  38. 40
    0
      client/src/index.html
  39. 13
    0
      client/src/manifest.json
  40. 18
    0
      client/src/pages/about/about.html
  41. 3
    0
      client/src/pages/about/about.scss
  42. 15
    0
      client/src/pages/about/about.ts
  43. 32
    0
      client/src/pages/auth/forgot/forgot.html
  44. 8
    0
      client/src/pages/auth/forgot/forgot.ts
  45. 33
    0
      client/src/pages/auth/login/login.html
  46. 32
    0
      client/src/pages/auth/login/login.ts
  47. 35
    0
      client/src/pages/auth/register/register.html
  48. 14
    0
      client/src/pages/auth/register/register.ts
  49. 40
    0
      client/src/pages/beer/beer-modal.html
  50. 49
    0
      client/src/pages/beer/beer-modal.ts
  51. 28
    0
      client/src/pages/beer/beer.html
  52. 3
    0
      client/src/pages/beer/beer.scss
  53. 51
    0
      client/src/pages/beer/beer.ts
  54. 21
    0
      client/src/pages/contact/contact.html
  55. 3
    0
      client/src/pages/contact/contact.scss
  56. 15
    0
      client/src/pages/contact/contact.ts
  57. 25
    0
      client/src/pages/home/home.html
  58. 3
    0
      client/src/pages/home/home.scss
  59. 20
    0
      client/src/pages/home/home.ts
  60. 6
    0
      client/src/pages/tabs/tabs.html
  61. 22
    0
      client/src/pages/tabs/tabs.ts
  62. 41
    0
      client/src/providers/beer-service.ts
  63. 25
    0
      client/src/providers/giphy-service.ts
  64. 30
    0
      client/src/service-worker.js
  65. 78
    0
      client/src/theme/variables.scss
  66. 26
    0
      client/tsconfig.json
  67. 11
    0
      client/tslint.json
  68. 2965
    0
      client/yarn.lock
  69. 24
    0
      server/.gitignore
  70. BIN
      server/.mvn/wrapper/maven-wrapper.jar
  71. 1
    0
      server/.mvn/wrapper/maven-wrapper.properties
  72. 15
    0
      server/README.md
  73. 233
    0
      server/mvnw
  74. 145
    0
      server/mvnw.cmd
  75. 87
    0
      server/pom.xml
  76. 29
    0
      server/src/main/java/com/example/DemoApplication.java
  77. 45
    0
      server/src/main/java/com/example/beer/Beer.java
  78. 25
    0
      server/src/main/java/com/example/beer/BeerCommandLineRunner.java
  79. 37
    0
      server/src/main/java/com/example/beer/BeerController.java
  80. 8
    0
      server/src/main/java/com/example/beer/BeerRepository.java
  81. 1
    0
      server/src/main/resources/application.properties
  82. 16
    0
      server/src/test/java/com/example/DemoApplicationTests.java

+ 17
- 0
client/.editorconfig View File

@@ -0,0 +1,17 @@
1
+# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
2
+# editorconfig.org
3
+
4
+root = true
5
+
6
+[*]
7
+indent_style = space
8
+indent_size = 2
9
+
10
+# We recommend you to keep these unchanged
11
+end_of_line = lf
12
+charset = utf-8
13
+trim_trailing_whitespace = true
14
+insert_final_newline = true
15
+
16
+[*.md]
17
+trim_trailing_whitespace = false

+ 34
- 0
client/.gitignore View File

@@ -0,0 +1,34 @@
1
+# Specifies intentionally untracked files to ignore when using Git
2
+# http://git-scm.com/docs/gitignore
3
+
4
+*~
5
+*.sw[mnpcod]
6
+*.log
7
+*.tmp
8
+*.tmp.*
9
+log.txt
10
+*.sublime-project
11
+*.sublime-workspace
12
+.vscode/
13
+npm-debug.log*
14
+
15
+.idea/
16
+.sass-cache/
17
+.tmp/
18
+.versions/
19
+coverage/
20
+dist/
21
+node_modules/
22
+tmp/
23
+temp/
24
+hooks/
25
+platforms/
26
+plugins/
27
+plugins/android.json
28
+plugins/ios.json
29
+www/
30
+$RECYCLE.BIN/
31
+
32
+.DS_Store
33
+Thumbs.db
34
+UserInterfaceState.xcuserstate

+ 40
- 0
client/config.xml View File

@@ -0,0 +1,40 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+<widget id="com.ionicframework.ionicauth613285" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+  <name>ionic-auth</name>
4
+  <description>An awesome Ionic/Cordova app.</description>
5
+  <author email="hi@ionicframework" href="http://ionicframework.com/">Ionic Framework Team</author>
6
+  <content src="index.html"/>
7
+  <access origin="*"/>
8
+  <allow-navigation href="http://ionic.local/*"/>
9
+  <allow-intent href="http://*/*"/>
10
+  <allow-intent href="https://*/*"/>
11
+  <allow-intent href="tel:*"/>
12
+  <allow-intent href="sms:*"/>
13
+  <allow-intent href="mailto:*"/>
14
+  <allow-intent href="geo:*"/>
15
+  <platform name="android">
16
+    <allow-intent href="market:*"/>
17
+  </platform>
18
+  <platform name="ios">
19
+    <allow-intent href="itms:*"/>
20
+    <allow-intent href="itms-apps:*"/>
21
+  </platform>
22
+  <preference name="webviewbounce" value="false"/>
23
+  <preference name="UIWebViewBounce" value="false"/>
24
+  <preference name="DisallowOverscroll" value="true"/>
25
+  <preference name="android-minSdkVersion" value="16"/>
26
+  <preference name="BackupWebStorage" value="none"/>
27
+  <preference name="SplashMaintainAspectRatio" value="true"/>
28
+  <preference name="FadeSplashScreenDuration" value="300"/>
29
+  <preference name="SplashShowOnlyFirstTime" value="false"/>
30
+  <preference name="KeyboardDisplayRequiresUserAction" value="false" />
31
+  <feature name="StatusBar">
32
+    <param name="ios-package" onload="true" value="CDVStatusBar"/>
33
+  </feature>
34
+  <plugin name="ionic-plugin-keyboard" spec="~2.2.1"/>
35
+  <plugin name="cordova-plugin-whitelist" spec="1.3.1"/>
36
+  <plugin name="cordova-plugin-console" spec="1.0.5"/>
37
+  <plugin name="cordova-plugin-statusbar" spec="2.2.1"/>
38
+  <plugin name="cordova-plugin-device" spec="1.1.4"/>
39
+  <plugin name="cordova-plugin-splashscreen" spec="~4.0.1"/>
40
+</widget>

+ 6
- 0
client/ionic.config.json View File

@@ -0,0 +1,6 @@
1
+{
2
+  "name": "ionic-auth",
3
+  "app_id": "",
4
+  "v2": true,
5
+  "typescript": true
6
+}

+ 52
- 0
client/package.json View File

@@ -0,0 +1,52 @@
1
+{
2
+  "name": "ionic-hello-world",
3
+  "author": "Ionic Framework",
4
+  "homepage": "http://ionicframework.com/",
5
+  "private": true,
6
+  "scripts": {
7
+    "clean": "ionic-app-scripts clean",
8
+    "build": "ionic-app-scripts build",
9
+    "ionic:build": "ionic-app-scripts build",
10
+    "ionic:serve": "ionic-app-scripts serve"
11
+  },
12
+  "dependencies": {
13
+    "@angular/common": "2.3.1",
14
+    "@angular/compiler": "2.3.1",
15
+    "@angular/compiler-cli": "2.3.1",
16
+    "@angular/core": "2.3.1",
17
+    "@angular/forms": "2.3.1",
18
+    "@angular/http": "2.3.1",
19
+    "@angular/platform-browser": "2.3.1",
20
+    "@angular/platform-browser-dynamic": "2.3.1",
21
+    "@angular/platform-server": "2.3.1",
22
+    "@ionic/storage": "1.1.7",
23
+    "angular-stormpath": "^0.1.2",
24
+    "ionic-angular": "2.0.0",
25
+    "ionic-native": "2.4.1",
26
+    "ionicons": "3.0.0",
27
+    "rxjs": "5.0.0-beta.12",
28
+    "sw-toolbox": "3.4.0",
29
+    "zone.js": "0.6.26"
30
+  },
31
+  "devDependencies": {
32
+    "@ionic/app-scripts": "1.0.0",
33
+    "typescript": "2.0.9"
34
+  },
35
+  "cordovaPlugins": [
36
+    "cordova-plugin-whitelist",
37
+    "cordova-plugin-console",
38
+    "cordova-plugin-statusbar",
39
+    "cordova-plugin-device",
40
+    "cordova-plugin-splashscreen",
41
+    "ionic-plugin-keyboard"
42
+  ],
43
+  "cordovaPlatforms": [
44
+    "ios",
45
+    {
46
+      "platform": "ios",
47
+      "version": "",
48
+      "locator": "ios"
49
+    }
50
+  ],
51
+  "description": "ionic-auth: An Ionic project"
52
+}

BIN
client/resources/android/splash/drawable-land-hdpi-screen.png View File


BIN
client/resources/android/splash/drawable-land-ldpi-screen.png View File


BIN
client/resources/android/splash/drawable-land-mdpi-screen.png View File


BIN
client/resources/android/splash/drawable-land-xhdpi-screen.png View File


BIN
client/resources/android/splash/drawable-land-xxhdpi-screen.png View File


BIN
client/resources/android/splash/drawable-land-xxxhdpi-screen.png View File


BIN
client/resources/android/splash/drawable-port-hdpi-screen.png View File


BIN
client/resources/android/splash/drawable-port-ldpi-screen.png View File


BIN
client/resources/android/splash/drawable-port-mdpi-screen.png View File


BIN
client/resources/android/splash/drawable-port-xhdpi-screen.png View File


BIN
client/resources/android/splash/drawable-port-xxhdpi-screen.png View File


BIN
client/resources/android/splash/drawable-port-xxxhdpi-screen.png View File


BIN
client/resources/icon.png View File


BIN
client/resources/ios/splash/Default-568h@2x~iphone.png View File


BIN
client/resources/ios/splash/Default-667h.png View File


BIN
client/resources/ios/splash/Default-736h.png View File


BIN
client/resources/ios/splash/Default-Landscape-736h.png View File


BIN
client/resources/ios/splash/Default-Landscape@2x~ipad.png View File


BIN
client/resources/ios/splash/Default-Landscape~ipad.png View File


BIN
client/resources/ios/splash/Default-Portrait@2x~ipad.png View File


BIN
client/resources/ios/splash/Default-Portrait~ipad.png View File


BIN
client/resources/ios/splash/Default@2x~iphone.png View File


BIN
client/resources/ios/splash/Default~iphone.png View File


BIN
client/resources/splash.png View File


+ 30
- 0
client/src/app/app.component.ts View File

@@ -0,0 +1,30 @@
1
+import { Component } from '@angular/core';
2
+import { Platform } from 'ionic-angular';
3
+import { StatusBar, Splashscreen } from 'ionic-native';
4
+import { TabsPage } from '../pages/tabs/tabs';
5
+import { Stormpath } from 'angular-stormpath';
6
+import { LoginPage } from '../pages/auth/login/login';
7
+
8
+@Component({
9
+  templateUrl: 'app.html'
10
+})
11
+export class MyApp {
12
+  rootPage;
13
+
14
+  constructor(platform: Platform, private stormpath: Stormpath) {
15
+    stormpath.user$.subscribe(user => {
16
+      if (!user) {
17
+        this.rootPage = LoginPage;
18
+      } else {
19
+        this.rootPage = TabsPage;
20
+      }
21
+    });
22
+
23
+    platform.ready().then(() => {
24
+      // Okay, so the platform is ready and our plugins are available.
25
+      // Here you can do any higher level native things you might need.
26
+      StatusBar.styleDefault();
27
+      Splashscreen.hide();
28
+    });
29
+  }
30
+}

+ 1
- 0
client/src/app/app.html View File

@@ -0,0 +1 @@
1
+<ion-nav [root]="rootPage"></ion-nav>

+ 60
- 0
client/src/app/app.module.ts View File

@@ -0,0 +1,60 @@
1
+import { NgModule, ErrorHandler } from '@angular/core';
2
+import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
3
+import { MyApp } from './app.component';
4
+import { AboutPage } from '../pages/about/about';
5
+import { ContactPage } from '../pages/contact/contact';
6
+import { HomePage } from '../pages/home/home';
7
+import { TabsPage } from '../pages/tabs/tabs';
8
+import { StormpathConfiguration, StormpathModule } from 'angular-stormpath';
9
+import { LoginPage } from '../pages/auth/login/login';
10
+import { ForgotPasswordPage } from '../pages/auth/forgot/forgot';
11
+import { RegisterPage } from '../pages/auth/register/register';
12
+import { BeerPage } from '../pages/beer/beer';
13
+import { BeerService } from '../providers/beer-service';
14
+import { GiphyService } from '../providers/giphy-service';
15
+import { BeerModalPage } from '../pages/beer/beer-modal';
16
+
17
+export function stormpathConfig(): StormpathConfiguration {
18
+  let spConfig: StormpathConfiguration = new StormpathConfiguration();
19
+  spConfig.endpointPrefix = 'http://localhost:8080';
20
+  spConfig.autoAuthorizedUris.push(new RegExp('http://localhost:8080/*'));
21
+  return spConfig;
22
+}
23
+
24
+@NgModule({
25
+  declarations: [
26
+    MyApp,
27
+    AboutPage,
28
+    ContactPage,
29
+    HomePage,
30
+    TabsPage,
31
+    LoginPage,
32
+    ForgotPasswordPage,
33
+    RegisterPage,
34
+    BeerPage,
35
+    BeerModalPage
36
+  ],
37
+  imports: [
38
+    IonicModule.forRoot(MyApp),
39
+    StormpathModule
40
+  ],
41
+  bootstrap: [IonicApp],
42
+  entryComponents: [
43
+    MyApp,
44
+    AboutPage,
45
+    ContactPage,
46
+    HomePage,
47
+    TabsPage,
48
+    LoginPage,
49
+    ForgotPasswordPage,
50
+    RegisterPage,
51
+    BeerPage,
52
+    BeerModalPage
53
+  ],
54
+  providers: [
55
+    {provide: ErrorHandler, useClass: IonicErrorHandler},
56
+    {provide: StormpathConfiguration, useFactory: stormpathConfig},
57
+    BeerService, GiphyService
58
+  ]
59
+})
60
+export class AppModule {}

+ 34
- 0
client/src/app/app.scss View File

@@ -0,0 +1,34 @@
1
+// http://ionicframework.com/docs/v2/theming/
2
+
3
+
4
+// App Global Sass
5
+// --------------------------------------------------
6
+// Put style rules here that you want to apply globally. These
7
+// styles are for the entire app and not just one component.
8
+// Additionally, this file can be also used as an entry point
9
+// to import other Sass files to be included in the output CSS.
10
+//
11
+// Shared Sass variables, which can be used to adjust Ionic's
12
+// default Sass variables, belong in "theme/variables.scss".
13
+//
14
+// To declare rules for a specific mode, create a child rule
15
+// for the .md, .ios, or .wp mode classes. The mode class is
16
+// automatically applied to the <body> element in the app.
17
+
18
+.alert {
19
+  padding: 15px;
20
+  margin-bottom: 20px;
21
+  border: 1px solid transparent;
22
+}
23
+
24
+.alert-danger {
25
+  color: #a94442;
26
+  background-color: #f2dede;
27
+  border-color: #ebccd1;
28
+}
29
+
30
+.alert-success {
31
+  color: #3c763d;
32
+  background-color: #dff0d8;
33
+  border-color: #d6e9c6;
34
+}

+ 5
- 0
client/src/app/main.ts View File

@@ -0,0 +1,5 @@
1
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2
+
3
+import { AppModule } from './app.module';
4
+
5
+platformBrowserDynamic().bootstrapModule(AppModule);

BIN
client/src/assets/imgs/logo.png View File


BIN
client/src/assets/imgs/stormpath-sdks.jpg View File


+ 14
- 0
client/src/declarations.d.ts View File

@@ -0,0 +1,14 @@
1
+/*
2
+  Declaration files are how the Typescript compiler knows about the type information(or shape) of an object.
3
+  They're what make intellisense work and make Typescript know all about your code.
4
+
5
+  A wildcard module is declared below to allow third party libraries to be used in an app even if they don't
6
+  provide their own type declarations.
7
+
8
+  To learn more about using third party libraries in an Ionic app, check out the docs here:
9
+  http://ionicframework.com/docs/v2/resources/third-party-libs/
10
+
11
+  For more info on type definition files, check out the Typescript docs here:
12
+  https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
13
+*/
14
+declare module '*';

+ 40
- 0
client/src/index.html View File

@@ -0,0 +1,40 @@
1
+<!DOCTYPE html>
2
+<html lang="en" dir="ltr">
3
+<head>
4
+  <meta charset="UTF-8">
5
+  <title>Ionic Beer</title>
6
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
+  <meta name="format-detection" content="telephone=no">
8
+  <meta name="msapplication-tap-highlight" content="no">
9
+
10
+  <link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
11
+  <link rel="manifest" href="manifest.json">
12
+  <meta name="theme-color" content="#4e8ef7">
13
+
14
+  <!-- cordova.js required for cordova apps -->
15
+  <script src="cordova.js"></script>
16
+
17
+  <!--<script>
18
+    if ('serviceWorker' in navigator) {
19
+      navigator.serviceWorker.register('service-worker.js')
20
+        .then(() => console.log('service worker installed'))
21
+        .catch(err => console.log('Error', err));
22
+    }
23
+  </script>-->
24
+
25
+  <link href="build/main.css" rel="stylesheet">
26
+
27
+</head>
28
+<body>
29
+
30
+  <!-- Ionic's root component and where the app will load -->
31
+  <ion-app></ion-app>
32
+
33
+  <!-- The polyfills js is generated during the build process -->
34
+  <script src="build/polyfills.js"></script>
35
+
36
+  <!-- The bundle js is generated during the build process -->
37
+  <script src="build/main.js"></script>
38
+
39
+</body>
40
+</html>

+ 13
- 0
client/src/manifest.json View File

@@ -0,0 +1,13 @@
1
+{
2
+  "name": "Ionic Auth",
3
+  "short_name": "Ionic Auth",
4
+  "start_url": "index.html",
5
+  "display": "standalone",
6
+  "icons": [{
7
+    "src": "assets/imgs/logo.png",
8
+    "sizes": "512x512",
9
+    "type": "image/png"
10
+  }],
11
+  "background_color": "#4e8ef7",
12
+  "theme_color": "#4e8ef7"
13
+}

+ 18
- 0
client/src/pages/about/about.html View File

@@ -0,0 +1,18 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>
4
+      About
5
+    </ion-title>
6
+  </ion-navbar>
7
+</ion-header>
8
+
9
+<ion-content padding>
10
+  <p><a href="https://stormpath.com">Stormpath</a> is an API service that allows developers to create, edit, and
11
+    securely store user accounts and user account data, and connect them with one or multiple applications. We make user
12
+    account management a lot easier, more secure, and infinitely scalable.</p>
13
+
14
+  <p>
15
+    Click the image below to see all the language and framework integrations we provide.
16
+    <a href="https://docs.stormpath.com"><img src="assets/imgs/stormpath-sdks.jpg" alt="Stormpath SDKs"></a>
17
+  </p>
18
+</ion-content>

+ 3
- 0
client/src/pages/about/about.scss View File

@@ -0,0 +1,3 @@
1
+page-about {
2
+
3
+}

+ 15
- 0
client/src/pages/about/about.ts View File

@@ -0,0 +1,15 @@
1
+import { Component } from '@angular/core';
2
+
3
+import { NavController } from 'ionic-angular';
4
+
5
+@Component({
6
+  selector: 'page-about',
7
+  templateUrl: 'about.html'
8
+})
9
+export class AboutPage {
10
+
11
+  constructor(public navCtrl: NavController) {
12
+
13
+  }
14
+
15
+}

+ 32
- 0
client/src/pages/auth/forgot/forgot.html View File

@@ -0,0 +1,32 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>
4
+      Forgot Password
5
+    </ion-title>
6
+  </ion-navbar>
7
+</ion-header>
8
+<ion-content padding>
9
+  <p *ngIf="sent">
10
+    We have sent a password reset link to the email address of the account that you specified.
11
+    Please check your email for this message, then click on the link.<br>
12
+    <button ion-button type="button" (click)="showLogin()">Back to Login</button>
13
+  </p>
14
+  <ion-row>
15
+    <ion-col>
16
+      <form *ngIf="!sent" #registerForm="ngForm" (ngSubmit)="onSubmit(registerForm.value)" autocomplete="off">
17
+        <ion-list inset>
18
+          <ion-item>
19
+            <ion-input name="email" type="email" id="spEmail" [(ngModel)]="forgotPasswordFormModel.email"
20
+                       placeholder="Your Email Address" [disabled]="posting" required></ion-input>
21
+          </ion-item>
22
+        </ion-list>
23
+        <ion-row>
24
+          <ion-col>
25
+            <p class="text-danger" *ngIf="error">{{error}}</p>
26
+            <button ion-button type="submit" full [disabled]="!registerForm.form.valid || posting">Request Password Reset</button>
27
+          </ion-col>
28
+        </ion-row>
29
+      </form>
30
+    </ion-col>
31
+  </ion-row>
32
+</ion-content>

+ 8
- 0
client/src/pages/auth/forgot/forgot.ts View File

@@ -0,0 +1,8 @@
1
+import { ForgotPasswordComponent } from 'angular-stormpath';
2
+import { Component } from '@angular/core';
3
+
4
+@Component({
5
+  selector: 'page-forgot-password',
6
+  templateUrl: './forgot.html'
7
+})
8
+export class ForgotPasswordPage extends ForgotPasswordComponent {}

+ 33
- 0
client/src/pages/auth/login/login.html View File

@@ -0,0 +1,33 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>
4
+      Login
5
+    </ion-title>
6
+  </ion-navbar>
7
+</ion-header>
8
+<ion-content padding>
9
+  <form #loginForm="ngForm" (ngSubmit)="login(loginForm.value)" autocomplete="off">
10
+    <ion-row>
11
+      <ion-col>
12
+        <ion-list inset>
13
+          <ion-item>
14
+            <ion-input placeholder="Email" name="login" id="loginField" type="text"
15
+                       required [(ngModel)]="loginFormModel.login" #email></ion-input>
16
+          </ion-item>
17
+          <ion-item>
18
+            <ion-input placeholder="Password" name="password" id="passwordField"
19
+                       type="password" required [(ngModel)]="loginFormModel.password"></ion-input>
20
+          </ion-item>
21
+        </ion-list>
22
+      </ion-col>
23
+    </ion-row>
24
+    <ion-row>
25
+      <ion-col>
26
+        <div *ngIf="error" class="alert alert-danger">{{error}}</div>
27
+        <button ion-button class="submit-btn" full type="submit" [disabled]="!loginForm.form.valid">Login</button>
28
+        <button ion-button class="forgot-btn" type="button" block clear (click)="forgot()">Forgot Password?</button>
29
+        <button ion-button class="create-btn" type="button" block clear (click)="register()">Create Account</button>
30
+      </ion-col>
31
+    </ion-row>
32
+  </form>
33
+</ion-content>

+ 32
- 0
client/src/pages/auth/login/login.ts View File

@@ -0,0 +1,32 @@
1
+import { Component, ViewChild } from '@angular/core';
2
+import { LoginComponent} from 'angular-stormpath';
3
+import { NavController } from 'ionic-angular';
4
+import { Stormpath, LoginService } from 'angular-stormpath';
5
+import { ForgotPasswordPage } from '../forgot/forgot';
6
+import { RegisterPage } from '../register/register';
7
+
8
+@Component({
9
+  selector: 'page-login',
10
+  templateUrl: './login.html'
11
+})
12
+export class LoginPage extends LoginComponent {
13
+  @ViewChild('email') email;
14
+
15
+  constructor(stormpath: Stormpath, loginService: LoginService, private nav: NavController) {
16
+    super(stormpath, loginService);
17
+  }
18
+
19
+  forgot() {
20
+    this.nav.push(ForgotPasswordPage);
21
+  }
22
+
23
+  register() {
24
+    this.nav.push(RegisterPage);
25
+  }
26
+
27
+  ionViewDidLoad() {
28
+    setTimeout(() => {
29
+      this.email.setFocus();
30
+    },150);
31
+  }
32
+}

+ 35
- 0
client/src/pages/auth/register/register.html View File

@@ -0,0 +1,35 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>
4
+      Create Account
5
+    </ion-title>
6
+  </ion-navbar>
7
+</ion-header>
8
+<ion-content padding>
9
+  <form *ngIf="!registered" (ngSubmit)="onSubmit()" autocomplete="off" #registerForm="ngForm">
10
+    <ion-row>
11
+      <ion-col>
12
+        <ion-list inset>
13
+          <ion-item *ngFor="let field of model?.form?.fields">
14
+            <ion-input [name]="field.name" [id]="field.name" [type]="field.type"
15
+                       [(ngModel)]="formModel[field.name]" [placeholder]="field.placeholder"
16
+                       [disabled]="creating" [required]="field.required"></ion-input>
17
+          </ion-item>
18
+        </ion-list>
19
+      </ion-col>
20
+    </ion-row>
21
+    <ion-row>
22
+      <ion-col>
23
+        <div *ngIf="error" class="alert alert-danger">{{error}}</div>
24
+        <button ion-button type="submit" full [disabled]="!registerForm.form.valid">Register</button>
25
+      </ion-col>
26
+    </ion-row>
27
+  </form>
28
+  <p *ngIf="unverified" class="alert alert-success">
29
+    Your account has been created and requires verification.
30
+    Please check your email for a verification link.
31
+  </p>
32
+  <p class="alert alert-success" *ngIf="canLogin">
33
+    Your account has been created, you may now log in.
34
+  </p>
35
+</ion-content>

+ 14
- 0
client/src/pages/auth/register/register.ts View File

@@ -0,0 +1,14 @@
1
+import { Component } from '@angular/core';
2
+import { RegisterComponent } from 'angular-stormpath';
3
+
4
+@Component({
5
+  selector: 'page-register',
6
+  templateUrl: './register.html'
7
+})
8
+export class RegisterPage extends RegisterComponent {
9
+
10
+  // fix for view model not always loading
11
+  ionViewDidLoad(): void {
12
+    super.ngOnInit();
13
+  }
14
+}

+ 40
- 0
client/src/pages/beer/beer-modal.html View File

@@ -0,0 +1,40 @@
1
+<ion-header>
2
+  <ion-toolbar>
3
+    <ion-title>
4
+      {{beer ? 'Beer Details' : 'Add Beer'}}
5
+    </ion-title>
6
+    <ion-buttons start>
7
+      <button ion-button (click)="dismiss()">
8
+        <span ion-text color="primary" showWhen="ios,core">Cancel</span>
9
+        <ion-icon name="md-close" showWhen="android,windows"></ion-icon>
10
+      </button>
11
+    </ion-buttons>
12
+  </ion-toolbar>
13
+</ion-header>
14
+<ion-content padding>
15
+  <form #beerForm="ngForm" (ngSubmit)="save(beerForm.value)">
16
+    <input type="hidden" name="href" [(ngModel)]="beer.href">
17
+    <ion-row>
18
+      <ion-col>
19
+        <ion-list inset>
20
+          <ion-item>
21
+            <ion-input placeholder="Beer Name" name="name" type="text"
22
+                       required [(ngModel)]="beer.name" #name></ion-input>
23
+          </ion-item>
24
+        </ion-list>
25
+      </ion-col>
26
+    </ion-row>
27
+    <ion-row>
28
+      <ion-col *ngIf="beer" text-center>
29
+        <img src="{{beer.giphyUrl}}">
30
+      </ion-col>
31
+    </ion-row>
32
+    <ion-row>
33
+      <ion-col>
34
+        <div *ngIf="error" class="alert alert-danger">{{error}}</div>
35
+        <button ion-button color="primary" full type="submit"
36
+                [disabled]="!beerForm.form.valid">Save</button>
37
+      </ion-col>
38
+    </ion-row>
39
+  </form>
40
+</ion-content>

+ 49
- 0
client/src/pages/beer/beer-modal.ts View File

@@ -0,0 +1,49 @@
1
+import { BeerService } from '../../providers/beer-service';
2
+import { Component } from '@angular/core';
3
+import { GiphyService } from '../../providers/giphy-service';
4
+import { NavParams, ViewController, ToastController, NavController } from 'ionic-angular';
5
+import { NgForm } from '@angular/forms';
6
+import { BeerPage } from './beer';
7
+
8
+@Component({
9
+  templateUrl: './beer-modal.html'
10
+})
11
+export class BeerModalPage {
12
+  beer: any = {};
13
+  error: any;
14
+
15
+  constructor(public beerService: BeerService,
16
+              public giphyService: GiphyService,
17
+              public params: NavParams,
18
+              public viewCtrl: ViewController,
19
+              public toastCtrl: ToastController,
20
+              public navCtrl: NavController) {
21
+    if (this.params.data.id) {
22
+      this.beerService.get(this.params.get('id')).subscribe(beer => {
23
+        this.beer = beer;
24
+        this.beer.href = beer._links.self.href;
25
+        this.giphyService.get(beer.name).subscribe(url => beer.giphyUrl = url);
26
+      });
27
+    }
28
+  }
29
+
30
+  dismiss() {
31
+    this.viewCtrl.dismiss();
32
+  }
33
+
34
+  save(form: NgForm) {
35
+    let update: boolean = form['href'];
36
+    this.beerService.save(form).subscribe(result => {
37
+      let toast = this.toastCtrl.create({
38
+        message: 'Beer "' + form.name + '" ' + ((update) ? 'updated' : 'added') + '.',
39
+        duration: 2000,
40
+        position: 'top'
41
+      });
42
+      toast.present();
43
+      // this should work, but doesn't refresh model
44
+      //this.viewCtrl.dismiss();
45
+      // this refreshes model
46
+      this.navCtrl.push(BeerPage);
47
+    }, error => this.error = error)
48
+  }
49
+}

+ 28
- 0
client/src/pages/beer/beer.html View File

@@ -0,0 +1,28 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>Good Beers</ion-title>
4
+    <ion-buttons end>
5
+      <button ion-button icon-only (click)="openModal()" color="primary">
6
+        <ion-icon name="add-circle"></ion-icon>
7
+        <ion-icon name="beer"></ion-icon>
8
+      </button>
9
+    </ion-buttons>
10
+  </ion-navbar>
11
+
12
+</ion-header>
13
+
14
+<ion-content padding>
15
+  <ion-list>
16
+    <ion-item-sliding *ngFor="let beer of beers" >
17
+      <ion-item (click)="openModal({id: beer.id})">
18
+        <ion-avatar item-left>
19
+          <img src="{{beer.giphyUrl}}">
20
+        </ion-avatar>
21
+        <h2>{{beer.name}}</h2>
22
+      </ion-item>
23
+      <ion-item-options>
24
+        <button ion-button color="danger" (click)="remove(beer)"><ion-icon name="trash"></ion-icon> Delete</button>
25
+      </ion-item-options>
26
+    </ion-item-sliding>
27
+  </ion-list>
28
+</ion-content>

+ 3
- 0
client/src/pages/beer/beer.scss View File

@@ -0,0 +1,3 @@
1
+page-beer {
2
+
3
+}

+ 51
- 0
client/src/pages/beer/beer.ts View File

@@ -0,0 +1,51 @@
1
+import { Component } from '@angular/core';
2
+import { NavController, NavParams, ViewController, ModalController, ToastController } from 'ionic-angular';
3
+import { BeerService } from '../../providers/beer-service';
4
+import { GiphyService } from '../../providers/giphy-service';
5
+import { BeerModalPage } from './beer-modal';
6
+
7
+@Component({
8
+  selector: 'page-beer',
9
+  templateUrl: 'beer.html',
10
+  providers: [BeerService, GiphyService]
11
+})
12
+export class BeerPage {
13
+  private beers: Array<any>;
14
+
15
+  constructor(public navCtrl: NavController, public navParams: NavParams,
16
+              public beerService: BeerService, public giphyService: GiphyService,
17
+              public modalCtrl: ModalController, public toastCtrl: ToastController) {
18
+  }
19
+
20
+  ionViewDidLoad() {
21
+    this.beerService.getGoodBeers().subscribe(beers => {
22
+      this.beers = beers;
23
+      for (let beer of this.beers) {
24
+        this.giphyService.get(beer.name).subscribe(url => {
25
+          beer.giphyUrl = url
26
+        });
27
+      }
28
+    })
29
+  }
30
+
31
+  openModal(beerId) {
32
+    let modal = this.modalCtrl.create(BeerModalPage, beerId);
33
+    modal.present();
34
+  }
35
+
36
+  remove(beer) {
37
+    this.beerService.remove(beer.id).subscribe(response => {
38
+      for (let i = 0; i < this.beers.length; i++) {
39
+        if (this.beers[i] === beer) {
40
+          this.beers.splice(i, 1);
41
+          let toast = this.toastCtrl.create({
42
+            message: 'Beer "' + beer.name + '" deleted successfully.',
43
+            duration: 2000,
44
+            position: 'top'
45
+          });
46
+          toast.present();
47
+        }
48
+      }
49
+    });
50
+  }
51
+}

+ 21
- 0
client/src/pages/contact/contact.html View File

@@ -0,0 +1,21 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>
4
+      Contact
5
+    </ion-title>
6
+  </ion-navbar>
7
+</ion-header>
8
+
9
+<ion-content>
10
+  <ion-list>
11
+    <ion-list-header>Follow us on Twitter</ion-list-header>
12
+    <ion-item>
13
+      <ion-icon name="ionic" item-left></ion-icon>
14
+      <a href="https://twitter.com/ionicframework">@ionicframework</a>
15
+    </ion-item>
16
+    <ion-item>
17
+      <ion-icon item-left><img src="assets/imgs/logo.png" width="24"></ion-icon>
18
+      <a href="https://twitter.com/gostormpath">@goStormpath</a>
19
+    </ion-item>
20
+  </ion-list>
21
+</ion-content>

+ 3
- 0
client/src/pages/contact/contact.scss View File

@@ -0,0 +1,3 @@
1
+page-contact {
2
+
3
+}

+ 15
- 0
client/src/pages/contact/contact.ts View File

@@ -0,0 +1,15 @@
1
+import { Component } from '@angular/core';
2
+
3
+import { NavController } from 'ionic-angular';
4
+
5
+@Component({
6
+  selector: 'page-contact',
7
+  templateUrl: 'contact.html'
8
+})
9
+export class ContactPage {
10
+
11
+  constructor(public navCtrl: NavController) {
12
+
13
+  }
14
+
15
+}

+ 25
- 0
client/src/pages/home/home.html View File

@@ -0,0 +1,25 @@
1
+<ion-header>
2
+  <ion-navbar>
3
+    <ion-title>Home</ion-title>
4
+    <ion-buttons end>
5
+      <button ion-button icon-only (click)="logout()">
6
+        Logout
7
+      </button>
8
+    </ion-buttons>
9
+  </ion-navbar>
10
+</ion-header>
11
+
12
+<ion-content padding>
13
+  <h2>Welcome to Ionic!</h2>
14
+  <p>
15
+    This starter project comes with simple tabs-based layout for apps
16
+    that are going to primarily use a Tabbed UI.
17
+  </p>
18
+  <p>
19
+    Take a look at the <code>src/pages/</code> directory to add or change tabs,
20
+    update any existing page or create new pages.
21
+  </p>
22
+  <p *ngIf="(user$ | async)">
23
+    You are logged in as: <b>{{ ( user$ | async ).fullName }}</b>
24
+  </p>
25
+</ion-content>

+ 3
- 0
client/src/pages/home/home.scss View File

@@ -0,0 +1,3 @@
1
+page-home {
2
+
3
+}

+ 20
- 0
client/src/pages/home/home.ts View File

@@ -0,0 +1,20 @@
1
+import { Component } from '@angular/core';
2
+
3
+import { Account, Stormpath } from 'angular-stormpath';
4
+import { Observable } from 'rxjs';
5
+
6
+@Component({
7
+  selector: 'page-home',
8
+  templateUrl: 'home.html'
9
+})
10
+export class HomePage {
11
+  user$: Observable<Account | boolean>;
12
+
13
+  constructor(private stormpath: Stormpath) {
14
+    this.user$ = this.stormpath.user$;
15
+  }
16
+
17
+  logout(): void {
18
+    this.stormpath.logout();
19
+  }
20
+}

+ 6
- 0
client/src/pages/tabs/tabs.html View File

@@ -0,0 +1,6 @@
1
+<ion-tabs>
2
+  <ion-tab [root]="tab1Root" tabTitle="Home" tabIcon="home"></ion-tab>
3
+  <ion-tab [root]="tab2Root" tabTitle="Beer" tabIcon="beer"></ion-tab>
4
+  <ion-tab [root]="tab3Root" tabTitle="Contact" tabIcon="contacts"></ion-tab>
5
+  <ion-tab [root]="tab4Root" tabTitle="About" tabIcon="information-circle"></ion-tab>
6
+</ion-tabs>

+ 22
- 0
client/src/pages/tabs/tabs.ts View File

@@ -0,0 +1,22 @@
1
+import { Component } from '@angular/core';
2
+
3
+import { HomePage } from '../home/home';
4
+import { AboutPage } from '../about/about';
5
+import { ContactPage } from '../contact/contact';
6
+import { BeerPage } from '../beer/beer';
7
+
8
+@Component({
9
+  templateUrl: 'tabs.html'
10
+})
11
+export class TabsPage {
12
+  // this tells the tabs component which Pages
13
+  // should be each tab's root Page
14
+  tab1Root: any = HomePage;
15
+  tab2Root: any = BeerPage;
16
+  tab3Root: any = ContactPage;
17
+  tab4Root: any = AboutPage;
18
+
19
+  constructor() {
20
+
21
+  }
22
+}

+ 41
- 0
client/src/providers/beer-service.ts View File

@@ -0,0 +1,41 @@
1
+import { Injectable } from '@angular/core';
2
+import { Http, Response } from '@angular/http';
3
+import 'rxjs/add/operator/map';
4
+import { Observable } from 'rxjs';
5
+
6
+@Injectable()
7
+export class BeerService {
8
+  public API = 'http://localhost:8080';
9
+  public BEER_API = this.API + '/beers';
10
+
11
+  constructor(public http: Http) {
12
+  }
13
+
14
+  getGoodBeers(): Observable<any> {
15
+    return this.http.get(this.API + '/good-beers')
16
+      .map((response: Response) => response.json());
17
+  }
18
+
19
+  get(id: string) {
20
+    return this.http.get(this.BEER_API + '/' + id)
21
+      .map((response: Response) => response.json());
22
+  }
23
+
24
+  remove(id: string) {
25
+    return this.http.delete(this.BEER_API + '/' + id)
26
+      .map((response: Response) => response.json());
27
+  }
28
+
29
+  save(beer: any): Observable<any> {
30
+    let result: Observable<Response>;
31
+    if (beer['href']) {
32
+      // get id from href
33
+      let id = beer.href.substring(beer.href.lastIndexOf('/'));
34
+      result = this.http.put(this.BEER_API + id, beer);
35
+    } else {
36
+      result = this.http.post(this.BEER_API, beer)
37
+    }
38
+    return result.map((response: Response) => response.json())
39
+      .catch(error => Observable.throw(error));
40
+  }
41
+}

+ 25
- 0
client/src/providers/giphy-service.ts View File

@@ -0,0 +1,25 @@
1
+import { Injectable } from '@angular/core';
2
+import { Http, Response } from '@angular/http';
3
+import { Observable } from 'rxjs';
4
+
5
+@Injectable()
6
+// http://tutorials.pluralsight.com/front-end-javascript/getting-started-with-angular-2-by-building-a-giphy-search-application
7
+export class GiphyService {
8
+
9
+  giphyApi = '//api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=';
10
+
11
+  constructor(public http: Http) {
12
+  }
13
+
14
+  get(searchTerm): Observable<any> {
15
+    let apiLink = this.giphyApi + searchTerm;
16
+    return this.http.request(apiLink).map((res: Response) => {
17
+      let results = res.json().data;
18
+      if (results.length > 0) {
19
+        return results[0].images.original.url;
20
+      } else {
21
+        return 'https://media.giphy.com/media/YaOxRsmrv9IeA/giphy.gif'; // dancing cat for 404
22
+      }
23
+    });
24
+  }
25
+}

+ 30
- 0
client/src/service-worker.js View File

@@ -0,0 +1,30 @@
1
+/**
2
+ * Check out https://googlechrome.github.io/sw-toolbox/docs/master/index.html for
3
+ * more info on how to use sw-toolbox to custom configure your service worker.
4
+ */
5
+
6
+
7
+'use strict';
8
+importScripts('./build/sw-toolbox.js');
9
+
10
+self.toolbox.options.cache = {
11
+  name: 'ionic-cache'
12
+};
13
+
14
+// pre-cache our key assets
15
+self.toolbox.precache(
16
+  [
17
+    './build/main.js',
18
+    './build/main.css',
19
+    './build/polyfills.js',
20
+    'index.html',
21
+    'manifest.json'
22
+  ]
23
+);
24
+
25
+// dynamically cache any other local assets
26
+self.toolbox.router.any('/*', self.toolbox.cacheFirst);
27
+
28
+// for any other requests go to the network, cache,
29
+// and then only use that cached resource if your user goes offline
30
+self.toolbox.router.default = self.toolbox.networkFirst;

+ 78
- 0
client/src/theme/variables.scss View File

@@ -0,0 +1,78 @@
1
+// Ionic Variables and Theming. For more info, please see:
2
+// http://ionicframework.com/docs/v2/theming/
3
+$font-path: "../assets/fonts";
4
+
5
+@import "ionic.globals";
6
+
7
+
8
+// Shared Variables
9
+// --------------------------------------------------
10
+// To customize the look and feel of this app, you can override
11
+// the Sass variables found in Ionic's source scss files.
12
+// To view all the possible Ionic variables, see:
13
+// http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/
14
+
15
+
16
+
17
+
18
+// Named Color Variables
19
+// --------------------------------------------------
20
+// Named colors makes it easy to reuse colors on various components.
21
+// It's highly recommended to change the default colors
22
+// to match your app's branding. Ionic uses a Sass map of
23
+// colors so you can add, rename and remove colors as needed.
24
+// The "primary" color is the only required color in the map.
25
+
26
+$colors: (
27
+  primary:    #387ef5,
28
+  secondary:  #32db64,
29
+  danger:     #f53d3d,
30
+  light:      #f4f4f4,
31
+  dark:       #222
32
+);
33
+
34
+
35
+// App iOS Variables
36
+// --------------------------------------------------
37
+// iOS only Sass variables can go here
38
+
39
+
40
+
41
+
42
+// App Material Design Variables
43
+// --------------------------------------------------
44
+// Material Design only Sass variables can go here
45
+
46
+
47
+
48
+
49
+// App Windows Variables
50
+// --------------------------------------------------
51
+// Windows only Sass variables can go here
52
+
53
+
54
+
55
+
56
+// App Theme
57
+// --------------------------------------------------
58
+// Ionic apps can have different themes applied, which can
59
+// then be future customized. This import comes last
60
+// so that the above variables are used and Ionic's
61
+// default are overridden.
62
+
63
+@import "ionic.theme.default";
64
+
65
+
66
+// Ionicons
67
+// --------------------------------------------------
68
+// The premium icon font for Ionic. For more info, please see:
69
+// http://ionicframework.com/docs/v2/ionicons/
70
+
71
+@import "ionic.ionicons";
72
+
73
+
74
+// Fonts
75
+// --------------------------------------------------
76
+
77
+@import "roboto";
78
+@import "noto-sans";

+ 26
- 0
client/tsconfig.json View File

@@ -0,0 +1,26 @@
1
+{
2
+  "compilerOptions": {
3
+    "allowSyntheticDefaultImports": true,
4
+    "declaration": false,
5
+    "emitDecoratorMetadata": true,
6
+    "experimentalDecorators": true,
7
+    "lib": [
8
+      "dom",
9
+      "es2015"
10
+    ],
11
+    "module": "es2015",
12
+    "moduleResolution": "node",
13
+    "sourceMap": true,
14
+    "target": "es5"
15
+  },
16
+  "include": [
17
+    "src/**/*.ts"
18
+  ],
19
+  "exclude": [
20
+    "node_modules"
21
+  ],
22
+  "compileOnSave": false,
23
+  "atom": {
24
+    "rewriteTsconfig": false
25
+  }
26
+}

+ 11
- 0
client/tslint.json View File

@@ -0,0 +1,11 @@
1
+{
2
+  "rules": {
3
+    "no-duplicate-variable": true,
4
+    "no-unused-variable": [
5
+      true
6
+    ]
7
+  },
8
+  "rulesDirectory": [
9
+    "node_modules/tslint-eslint-rules/dist/rules"
10
+  ]
11
+}

+ 2965
- 0
client/yarn.lock
File diff suppressed because it is too large
View File


+ 24
- 0
server/.gitignore View File

@@ -0,0 +1,24 @@
1
+target/
2
+!.mvn/wrapper/maven-wrapper.jar
3
+
4
+### STS ###
5
+.apt_generated
6
+.classpath
7
+.factorypath
8
+.project
9
+.settings
10
+.springBeans
11
+
12
+### IntelliJ IDEA ###
13
+.idea
14
+*.iws
15
+*.iml
16
+*.ipr
17
+
18
+### NetBeans ###
19
+nbproject/private/
20
+build/
21
+nbbuild/
22
+dist/
23
+nbdist/
24
+.nb-gradle/

BIN
server/.mvn/wrapper/maven-wrapper.jar View File


+ 1
- 0
server/.mvn/wrapper/maven-wrapper.properties View File

@@ -0,0 +1 @@
1
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip

+ 15
- 0
server/README.md View File

@@ -0,0 +1,15 @@
1
+## Spring Boot with Stormpath
2
+
3
+If you already have Stormpath configured, you should be able to start this app with `mvn spring-boot:run`. 
4
+
5
+See instructions below if you don't have a Stormpath account.
6
+
7
+## Stormpath Setup
8
+
9
+The [Stormpath Spring Boot Quickstart](https://docs.stormpath.com/java/spring-boot-web/quickstart.html) shows how to create a Stormpath account and download an API key; here’s the abridged version:
10
+
11
+1. Signup for a free Stormpath developer account at https://api.stormpath.com/register.
12
+2. From the Home tab of the Admin Console select Manage API Keys under the Developer Tools heading.
13
+3. Click the Create API Key button to trigger a download of a apiKey-{API_KEY}.properties file.
14
+4. Move the file to `~/.stormpath/apiKey.properties`.
15
+

+ 233
- 0
server/mvnw View File

@@ -0,0 +1,233 @@
1
+#!/bin/sh
2
+# ----------------------------------------------------------------------------
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+#    http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+# KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+# ----------------------------------------------------------------------------
20
+
21
+# ----------------------------------------------------------------------------
22
+# Maven2 Start Up Batch script
23
+#
24
+# Required ENV vars:
25
+# ------------------
26
+#   JAVA_HOME - location of a JDK home dir
27
+#
28
+# Optional ENV vars
29
+# -----------------
30
+#   M2_HOME - location of maven2's installed home dir
31
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
32
+#     e.g. to debug Maven itself, use
33
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35
+# ----------------------------------------------------------------------------
36
+
37
+if [ -z "$MAVEN_SKIP_RC" ] ; then
38
+
39
+  if [ -f /etc/mavenrc ] ; then
40
+    . /etc/mavenrc
41
+  fi
42
+
43
+  if [ -f "$HOME/.mavenrc" ] ; then
44
+    . "$HOME/.mavenrc"
45
+  fi
46
+
47
+fi
48
+
49
+# OS specific support.  $var _must_ be set to either true or false.
50
+cygwin=false;
51
+darwin=false;
52
+mingw=false
53
+case "`uname`" in
54
+  CYGWIN*) cygwin=true ;;
55
+  MINGW*) mingw=true;;
56
+  Darwin*) darwin=true
57
+           #
58
+           # Look for the Apple JDKs first to preserve the existing behaviour, and then look
59
+           # for the new JDKs provided by Oracle.
60
+           #
61
+           if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
62
+             #
63
+             # Apple JDKs
64
+             #
65
+             export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
66
+           fi
67
+
68
+           if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
69
+             #
70
+             # Apple JDKs
71
+             #
72
+             export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
73
+           fi
74
+
75
+           if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
76
+             #
77
+             # Oracle JDKs
78
+             #
79
+             export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
80
+           fi
81
+
82
+           if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
83
+             #
84
+             # Apple JDKs
85
+             #
86
+             export JAVA_HOME=`/usr/libexec/java_home`
87
+           fi
88
+           ;;
89
+esac
90
+
91
+if [ -z "$JAVA_HOME" ] ; then
92
+  if [ -r /etc/gentoo-release ] ; then
93
+    JAVA_HOME=`java-config --jre-home`
94
+  fi
95
+fi
96
+
97
+if [ -z "$M2_HOME" ] ; then
98
+  ## resolve links - $0 may be a link to maven's home
99
+  PRG="$0"
100
+
101
+  # need this for relative symlinks
102
+  while [ -h "$PRG" ] ; do
103
+    ls=`ls -ld "$PRG"`
104
+    link=`expr "$ls" : '.*-> \(.*\)$'`
105
+    if expr "$link" : '/.*' > /dev/null; then
106
+      PRG="$link"
107
+    else
108
+      PRG="`dirname "$PRG"`/$link"
109
+    fi
110
+  done
111
+
112
+  saveddir=`pwd`
113
+
114
+  M2_HOME=`dirname "$PRG"`/..
115
+
116
+  # make it fully qualified
117
+  M2_HOME=`cd "$M2_HOME" && pwd`
118
+
119
+  cd "$saveddir"
120
+  # echo Using m2 at $M2_HOME
121
+fi
122
+
123
+# For Cygwin, ensure paths are in UNIX format before anything is touched
124
+if $cygwin ; then
125
+  [ -n "$M2_HOME" ] &&
126
+    M2_HOME=`cygpath --unix "$M2_HOME"`
127
+  [ -n "$JAVA_HOME" ] &&
128
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
129
+  [ -n "$CLASSPATH" ] &&
130
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
131
+fi
132
+
133
+# For Migwn, ensure paths are in UNIX format before anything is touched
134
+if $mingw ; then
135
+  [ -n "$M2_HOME" ] &&
136
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
137
+  [ -n "$JAVA_HOME" ] &&
138
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
139
+  # TODO classpath?
140
+fi
141
+
142
+if [ -z "$JAVA_HOME" ]; then
143
+  javaExecutable="`which javac`"
144
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
145
+    # readlink(1) is not available as standard on Solaris 10.
146
+    readLink=`which readlink`
147
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
148
+      if $darwin ; then
149
+        javaHome="`dirname \"$javaExecutable\"`"
150
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
151
+      else
152
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
153
+      fi
154
+      javaHome="`dirname \"$javaExecutable\"`"
155
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
156
+      JAVA_HOME="$javaHome"
157
+      export JAVA_HOME
158
+    fi
159
+  fi
160
+fi
161
+
162
+if [ -z "$JAVACMD" ] ; then
163
+  if [ -n "$JAVA_HOME"  ] ; then
164
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
165
+      # IBM's JDK on AIX uses strange locations for the executables
166
+      JAVACMD="$JAVA_HOME/jre/sh/java"
167
+    else
168
+      JAVACMD="$JAVA_HOME/bin/java"
169
+    fi
170
+  else
171
+    JAVACMD="`which java`"
172
+  fi
173
+fi
174
+
175
+if [ ! -x "$JAVACMD" ] ; then
176
+  echo "Error: JAVA_HOME is not defined correctly." >&2
177
+  echo "  We cannot execute $JAVACMD" >&2
178
+  exit 1
179
+fi
180
+
181
+if [ -z "$JAVA_HOME" ] ; then
182
+  echo "Warning: JAVA_HOME environment variable is not set."
183
+fi
184
+
185
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186
+
187
+# For Cygwin, switch paths to Windows format before running java
188
+if $cygwin; then
189
+  [ -n "$M2_HOME" ] &&
190
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
191
+  [ -n "$JAVA_HOME" ] &&
192
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
193
+  [ -n "$CLASSPATH" ] &&
194
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
195
+fi
196
+
197
+# traverses directory structure from process work directory to filesystem root
198
+# first directory with .mvn subdirectory is considered project base directory
199
+find_maven_basedir() {
200
+  local basedir=$(pwd)
201
+  local wdir=$(pwd)
202
+  while [ "$wdir" != '/' ] ; do
203
+    if [ -d "$wdir"/.mvn ] ; then
204
+      basedir=$wdir
205
+      break
206
+    fi
207
+    wdir=$(cd "$wdir/.."; pwd)
208
+  done
209
+  echo "${basedir}"
210
+}
211
+
212
+# concatenates all lines of a file
213
+concat_lines() {
214
+  if [ -f "$1" ]; then
215
+    echo "$(tr -s '\n' ' ' < "$1")"
216
+  fi
217
+}
218
+
219
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
220
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
221
+
222
+# Provide a "standardized" way to retrieve the CLI args that will
223
+# work with both Windows and non-Windows executions.
224
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
225
+export MAVEN_CMD_LINE_ARGS
226
+
227
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
228
+
229
+exec "$JAVACMD" \
230
+  $MAVEN_OPTS \
231
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
232
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
233
+  ${WRAPPER_LAUNCHER} "$@"

+ 145
- 0
server/mvnw.cmd View File

@@ -0,0 +1,145 @@
1
+@REM ----------------------------------------------------------------------------
2
+@REM Licensed to the Apache Software Foundation (ASF) under one
3
+@REM or more contributor license agreements.  See the NOTICE file
4
+@REM distributed with this work for additional information
5
+@REM regarding copyright ownership.  The ASF licenses this file
6
+@REM to you under the Apache License, Version 2.0 (the
7
+@REM "License"); you may not use this file except in compliance
8
+@REM with the License.  You may obtain a copy of the License at
9
+@REM
10
+@REM    http://www.apache.org/licenses/LICENSE-2.0
11
+@REM
12
+@REM Unless required by applicable law or agreed to in writing,
13
+@REM software distributed under the License is distributed on an
14
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+@REM KIND, either express or implied.  See the License for the
16
+@REM specific language governing permissions and limitations
17
+@REM under the License.
18
+@REM ----------------------------------------------------------------------------
19
+
20
+@REM ----------------------------------------------------------------------------
21
+@REM Maven2 Start Up Batch script
22
+@REM
23
+@REM Required ENV vars:
24
+@REM JAVA_HOME - location of a JDK home dir
25
+@REM
26
+@REM Optional ENV vars
27
+@REM M2_HOME - location of maven2's installed home dir
28
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31
+@REM     e.g. to debug Maven itself, use
32
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34
+@REM ----------------------------------------------------------------------------
35
+
36
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37
+@echo off
38
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
40
+
41
+@REM set %HOME% to equivalent of $HOME
42
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43
+
44
+@REM Execute a user defined script before this one
45
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
47
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49
+:skipRcPre
50
+
51
+@setlocal
52
+
53
+set ERROR_CODE=0
54
+
55
+@REM To isolate internal variables from possible post scripts, we use another setlocal
56
+@setlocal
57
+
58
+@REM ==== START VALIDATION ====
59
+if not "%JAVA_HOME%" == "" goto OkJHome
60
+
61
+echo.
62
+echo Error: JAVA_HOME not found in your environment. >&2
63
+echo Please set the JAVA_HOME variable in your environment to match the >&2
64
+echo location of your Java installation. >&2
65
+echo.
66
+goto error
67
+
68
+:OkJHome
69
+if exist "%JAVA_HOME%\bin\java.exe" goto init
70
+
71
+echo.
72
+echo Error: JAVA_HOME is set to an invalid directory. >&2
73
+echo JAVA_HOME = "%JAVA_HOME%" >&2
74
+echo Please set the JAVA_HOME variable in your environment to match the >&2
75
+echo location of your Java installation. >&2
76
+echo.
77
+goto error
78
+
79
+@REM ==== END VALIDATION ====
80
+
81
+:init
82
+
83
+set MAVEN_CMD_LINE_ARGS=%*
84
+
85
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86
+@REM Fallback to current working directory if not found.
87
+
88
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90
+
91
+set EXEC_DIR=%CD%
92
+set WDIR=%EXEC_DIR%
93
+:findBaseDir
94
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
95
+cd ..
96
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
97
+set WDIR=%CD%
98
+goto findBaseDir
99
+
100
+:baseDirFound
101
+set MAVEN_PROJECTBASEDIR=%WDIR%
102
+cd "%EXEC_DIR%"
103
+goto endDetectBaseDir
104
+
105
+:baseDirNotFound
106
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107
+cd "%EXEC_DIR%"
108
+
109
+:endDetectBaseDir
110
+
111
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112
+
113
+@setlocal EnableExtensions EnableDelayedExpansion
114
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116
+
117
+:endReadAdditionalConfig
118
+
119
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120
+
121
+set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
122
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
123
+
124
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
125
+if ERRORLEVEL 1 goto error
126
+goto end
127
+
128
+:error
129
+set ERROR_CODE=1
130
+
131
+:end
132
+@endlocal & set ERROR_CODE=%ERROR_CODE%
133
+
134
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
135
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
136
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
137
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
138
+:skipRcPost
139
+
140
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
141
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
142
+
143
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
144
+
145
+exit /B %ERROR_CODE%

+ 87
- 0
server/pom.xml View File

@@ -0,0 +1,87 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4
+    <modelVersion>4.0.0</modelVersion>
5
+
6
+    <groupId>com.example</groupId>
7
+    <artifactId>demo</artifactId>
8
+    <version>0.0.1-SNAPSHOT</version>
9
+    <packaging>jar</packaging>
10
+
11
+    <name>demo</name>
12
+    <description>Demo project for Spring Boot</description>
13
+
14
+    <parent>
15
+        <groupId>org.springframework.boot</groupId>
16
+        <artifactId>spring-boot-starter-parent</artifactId>
17
+        <version>1.5.1.RELEASE</version>
18
+        <relativePath/> <!-- lookup parent from repository -->
19
+    </parent>
20
+
21
+    <properties>
22
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
24
+        <java.version>1.8</java.version>
25
+    </properties>
26
+
27
+    <dependencies>
28
+        <dependency>
29
+            <groupId>org.springframework.boot</groupId>
30
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
31
+        </dependency>
32
+        <dependency>
33
+            <groupId>org.springframework.boot</groupId>
34
+            <artifactId>spring-boot-starter-data-rest</artifactId>
35
+        </dependency>
36
+        <dependency>
37
+            <groupId>org.springframework.boot</groupId>
38
+            <artifactId>spring-boot-starter-security</artifactId>
39
+        </dependency>
40
+        <dependency>
41
+            <groupId>com.stormpath.spring</groupId>
42
+            <artifactId>stormpath-default-spring-boot-starter</artifactId>
43
+        </dependency>
44
+        <dependency>
45
+            <groupId>org.springframework.boot</groupId>
46
+            <artifactId>spring-boot-starter-web</artifactId>
47
+        </dependency>
48
+        <dependency>
49
+            <groupId>org.springframework.boot</groupId>
50
+            <artifactId>spring-boot-devtools</artifactId>
51
+            <scope>runtime</scope>
52
+        </dependency>
53
+        <dependency>
54
+            <groupId>com.h2database</groupId>
55
+            <artifactId>h2</artifactId>
56
+            <scope>runtime</scope>
57
+        </dependency>
58
+        <dependency>
59
+            <groupId>org.springframework.boot</groupId>
60
+            <artifactId>spring-boot-starter-test</artifactId>
61
+            <scope>test</scope>
62
+        </dependency>
63
+    </dependencies>
64
+
65
+    <dependencyManagement>
66
+        <dependencies>
67
+            <dependency>
68
+                <groupId>com.stormpath.sdk</groupId>
69
+                <artifactId>stormpath-bom</artifactId>
70
+                <version>1.5.0</version>
71
+                <type>pom</type>
72
+                <scope>import</scope>
73
+            </dependency>
74
+        </dependencies>
75
+    </dependencyManagement>
76
+
77
+    <build>
78
+        <defaultGoal>spring-boot:run</defaultGoal>
79
+        <plugins>
80
+            <plugin>
81
+                <groupId>org.springframework.boot</groupId>
82
+                <artifactId>spring-boot-maven-plugin</artifactId>
83
+            </plugin>
84
+        </plugins>
85
+    </build>
86
+
87
+</project>

+ 29
- 0
server/src/main/java/com/example/DemoApplication.java View File

@@ -0,0 +1,29 @@
1
+package com.example;
2
+
3
+import org.springframework.boot.CommandLineRunner;
4
+import org.springframework.boot.SpringApplication;
5
+import org.springframework.boot.autoconfigure.SpringBootApplication;
6
+import org.springframework.data.jpa.repository.JpaRepository;
7
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
8
+import org.springframework.stereotype.Component;
9
+import org.springframework.web.bind.annotation.GetMapping;
10
+import org.springframework.web.bind.annotation.RestController;
11
+
12
+import javax.persistence.Entity;
13
+import javax.persistence.GeneratedValue;
14
+import javax.persistence.Id;
15
+import java.util.Collection;
16
+import java.util.HashMap;
17
+import java.util.Map;
18
+import java.util.stream.Collectors;
19
+import java.util.stream.Stream;
20
+
21
+@SpringBootApplication
22
+public class DemoApplication {
23
+
24
+    public static void main(String[] args) {
25
+        SpringApplication.run(DemoApplication.class, args);
26
+    }
27
+}
28
+
29
+

+ 45
- 0
server/src/main/java/com/example/beer/Beer.java View File

@@ -0,0 +1,45 @@
1
+package com.example.beer;
2
+
3
+import javax.persistence.Entity;
4
+import javax.persistence.GeneratedValue;
5
+import javax.persistence.Id;
6
+
7
+@Entity
8
+public class Beer {
9
+
10
+    @Id
11
+    @GeneratedValue
12
+    private Long id;
13
+    private String name;
14
+
15
+    public Beer() {
16
+    }
17
+
18
+    public Beer(String name) {
19
+        this.name = name;
20
+    }
21
+
22
+    public Long getId() {
23
+        return id;
24
+    }
25
+
26
+    public void setId(Long id) {
27
+        this.id = id;
28
+    }
29
+
30
+    public String getName() {
31
+        return name;
32
+    }
33
+
34
+    public void setName(String name) {
35
+        this.name = name;
36
+    }
37
+
38
+    @Override
39
+    public String toString() {
40
+        return "Beer{" +
41
+                "id=" + id +
42
+                ", name='" + name + '\'' +
43
+                '}';
44
+    }
45
+}

+ 25
- 0
server/src/main/java/com/example/beer/BeerCommandLineRunner.java View File

@@ -0,0 +1,25 @@
1
+package com.example.beer;
2
+
3
+import org.springframework.boot.CommandLineRunner;
4
+import org.springframework.stereotype.Component;
5
+
6
+import java.util.stream.Stream;
7
+
8
+@Component
9
+class BeerCommandLineRunner implements CommandLineRunner {
10
+
11
+    public BeerCommandLineRunner(BeerRepository repository) {
12
+        this.repository = repository;
13
+    }
14
+
15
+    @Override
16
+    public void run(String... strings) throws Exception {
17
+        // top 5 beers from https://www.beeradvocate.com/lists/top/
18
+        Stream.of("Good Morning", "Kentucky Brunch Brand Stout", "ManBearPig", "King Julius", "Very Hazy", "Budweiser", "Coors Light", "PBR").forEach(name ->
19
+                repository.save(new Beer(name))
20
+        );
21
+        System.out.println(repository.findAll());
22
+    }
23
+
24
+    private final BeerRepository repository;
25
+}

+ 37
- 0
server/src/main/java/com/example/beer/BeerController.java View File

@@ -0,0 +1,37 @@
1
+package com.example.beer;
2
+
3
+import org.springframework.web.bind.annotation.GetMapping;
4
+import org.springframework.web.bind.annotation.RestController;
5
+
6
+import java.util.Collection;
7
+import java.util.HashMap;
8
+import java.util.Map;
9
+import java.util.stream.Collectors;
10
+
11
+@RestController
12
+public class BeerController {
13
+    private BeerRepository repository;
14
+
15
+    public BeerController(BeerRepository repository) {
16
+        this.repository = repository;
17
+    }
18
+
19
+    @GetMapping("/good-beers")
20
+    public Collection<Map<String, String>> goodBeers() {
21
+
22
+        return repository.findAll().stream()
23
+                .filter(this::isGreat)
24
+                .map(b -> {
25
+                    Map<String, String> m = new HashMap<>();
26
+                    m.put("id", b.getId().toString());
27
+                    m.put("name", b.getName());
28
+                    return m;
29
+                }).collect(Collectors.toList());
30
+    }
31
+
32
+    private boolean isGreat(Beer beer) {
33
+        return !beer.getName().equals("Budweiser") &&
34
+                !beer.getName().equals("Coors Light") &&
35
+                !beer.getName().equals("PBR");
36
+    }
37
+}

+ 8
- 0
server/src/main/java/com/example/beer/BeerRepository.java View File

@@ -0,0 +1,8 @@
1
+package com.example.beer;
2
+
3
+import org.springframework.data.jpa.repository.JpaRepository;
4
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
5
+
6
+@RepositoryRestResource
7
+interface BeerRepository extends JpaRepository<Beer, Long> {
8
+}

+ 1
- 0
server/src/main/resources/application.properties View File

@@ -0,0 +1 @@
1
+stormpath.web.cors.allowed.originUris = http://localhost:8100,file:// 

+ 16
- 0
server/src/test/java/com/example/DemoApplicationTests.java View File

@@ -0,0 +1,16 @@
1
+package com.example;
2
+
3
+import org.junit.Test;
4
+import org.junit.runner.RunWith;
5
+import org.springframework.boot.test.context.SpringBootTest;
6
+import org.springframework.test.context.junit4.SpringRunner;
7
+
8
+@RunWith(SpringRunner.class)
9
+@SpringBootTest
10
+public class DemoApplicationTests {
11
+
12
+	@Test
13
+	public void contextLoads() {
14
+	}
15
+
16
+}