Selaa lähdekoodia

зачатки вебморды + авторизация по токену

kpmy 5 vuotta sitten
vanhempi
commit
24b4886fd6
41 muutettua tiedostoa jossa 413 lisäystä ja 187 poistoa
  1. 3 1
      .drone.yml
  2. 16 5
      pom.xml
  3. 1 0
      src/app/angular.json
  4. 4 4
      src/app/package.json
  5. 8 0
      src/app/proxyconf.json
  6. 8 2
      src/app/src/app/app-routing.module.ts
  7. 12 1
      src/app/src/app/app.component.html
  8. 5 1
      src/app/src/app/app.component.ts
  9. 7 0
      src/app/src/app/app.index.component.html
  10. 9 0
      src/app/src/app/app.index.component.ts
  11. 13 0
      src/app/src/app/app.login.component.html
  12. 47 0
      src/app/src/app/app.login.component.ts
  13. 35 4
      src/app/src/app/app.module.ts
  14. 31 0
      src/app/src/app/person/person.service.ts
  15. BIN
      src/app/src/assets/android-chrome-192x192.png
  16. BIN
      src/app/src/assets/android-chrome-512x512.png
  17. BIN
      src/app/src/assets/apple-touch-icon.png
  18. BIN
      src/app/src/assets/favicon-16x16.png
  19. BIN
      src/app/src/assets/favicon-32x32.png
  20. BIN
      src/app/src/assets/favicon.ico
  21. 1 0
      src/app/src/assets/frei.webmanifest
  22. BIN
      src/app/src/favicon.ico
  23. 3 2
      src/app/src/index.html
  24. 9 0
      src/app/src/styles.scss
  25. 2 1
      src/app/tsconfig.json
  26. 0 148
      src/app/tslint.json
  27. 5 0
      src/app/yarn.lock
  28. 1 1
      src/main/java/inn/ocsf/bee/freigeld/cl/DryRunnable.java
  29. 1 1
      src/main/java/inn/ocsf/bee/freigeld/cl/KernelTri.java
  30. 1 1
      src/main/java/inn/ocsf/bee/freigeld/cl/Pt.java
  31. 1 1
      src/main/java/inn/ocsf/bee/freigeld/cl/Sample0.java
  32. 1 1
      src/main/java/inn/ocsf/bee/freigeld/cl/Sample1.java
  33. 4 0
      src/main/java/inn/ocsf/bee/freigeld/cl/Sample3.java
  34. 0 4
      src/main/java/inn/ocsf/bee/freigeld/core/cl/Sample3.java
  35. 1 1
      src/main/kotlin/inn/ocsf/bee/freigeld/FreiApp.kt
  36. 12 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/data/GlobalEmitterImpl.kt
  37. 9 7
      src/main/kotlin/inn/ocsf/bee/freigeld/core/demo/DemoInMem.kt
  38. 7 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/model/PersonIdentityTypes.kt
  39. 96 0
      src/main/kotlin/inn/ocsf/bee/freigeld/serve/rest/PersonController.kt
  40. 59 0
      src/main/kotlin/inn/ocsf/bee/freigeld/utils/KeyValueStorage.kt
  41. 1 1
      src/main/resources/application.yaml

+ 3 - 1
.drone.yml

@@ -4,8 +4,10 @@ pipeline:
     commands:
       - cd src/app
       - yarn
-      - yarn ng build --prod
+      - yarn build
       - rm -rf ./node_modules
+      - mkdir -p ./src/main/resources/static
+      - cp ./dist/frei-app/* ./src/main/resources/static
 
   build-backend:
     image: maven:3.3.9-jdk-8

+ 16 - 5
pom.xml

@@ -13,7 +13,7 @@
         <java.version>1.8</java.version>
         <kotlin.version>1.3.72</kotlin.version>
         <target>1.8</target>
-        <start-class>inn.ocsf.bee.freigeld.core.FreiApp</start-class>
+        <start-class>inn.ocsf.bee.freigeld.FreiApp</start-class>
         <spring.version>2.2.6.RELEASE</spring.version>
     </properties>
 
@@ -55,6 +55,12 @@
             <version>${spring.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>com.querydsl</groupId>
             <artifactId>querydsl-mongodb</artifactId>
@@ -67,7 +73,6 @@
             <version>${querydsl.version}</version>
         </dependency>
 
-
         <dependency>
             <groupId>org.jetbrains.kotlin</groupId>
             <artifactId>kotlin-stdlib-jdk8</artifactId>
@@ -127,9 +132,15 @@
         </dependency>
 
         <dependency>
-            <groupId>org.jboss.aerogear</groupId>
-            <artifactId>aerogear-otp-java</artifactId>
-            <version>1.0.0</version>
+            <groupId>dev.samstevens.totp</groupId>
+            <artifactId>totp</artifactId>
+            <version>1.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>eu.maxschuster</groupId>
+            <artifactId>dataurl</artifactId>
+            <version>2.0.0</version>
         </dependency>
 
         <dependency>

+ 1 - 0
src/app/angular.json

@@ -67,6 +67,7 @@
         "serve": {
           "builder": "@angular-devkit/build-angular:dev-server",
           "options": {
+            "port": 80,
             "browserTarget": "frei-app:build"
           },
           "configurations": {

+ 4 - 4
src/app/package.json

@@ -2,9 +2,8 @@
   "name": "frei-app",
   "version": "0.0.1",
   "scripts": {
-    "ng": "npx ng",
-    "start": "npx ng serve",
-    "build": "npx ng build"
+    "start": "npx ng serve --aot --source-map --proxy-config proxyconf.json --base-href / --serve-path /",
+    "build": "npx ng build --prod --base-href /"
   },
   "private": true,
   "dependencies": {
@@ -13,14 +12,15 @@
     "@angular/common": "~9.1.4",
     "@angular/compiler": "~9.1.4",
     "@angular/core": "~9.1.4",
+    "@angular/flex-layout": "^9.0.0-beta.29",
     "@angular/forms": "~9.1.4",
     "@angular/material": "^9.2.2",
     "@angular/platform-browser": "~9.1.4",
     "@angular/platform-browser-dynamic": "~9.1.4",
-    "@angular/flex-layout": "^9.0.0-beta.29",
     "@angular/router": "~9.1.4",
     "rxjs": "~6.5.4",
     "tslib": "^1.10.0",
+    "uuid": "^8.0.0",
     "zone.js": "~0.10.2"
   },
   "devDependencies": {

+ 8 - 0
src/app/proxyconf.json

@@ -0,0 +1,8 @@
+{
+  "/api": {
+    "target": "http://127.0.0.1:4200/api",
+    "pathRewrite": {
+      "^/api": ""
+    }
+  }
+}

+ 8 - 2
src/app/src/app/app-routing.module.ts

@@ -1,11 +1,17 @@
 import {NgModule} from '@angular/core';
 import {RouterModule, Routes} from '@angular/router';
+import {AppLoginComponent} from "./app.login.component";
+import {AppIndexComponent} from "./app.index.component";
 
 
-const routes: Routes = [];
+const routes: Routes = [
+  {path: 'frei', component: AppIndexComponent},
+  {path: 'login', component: AppLoginComponent},
+  {path: '', redirectTo: 'frei', pathMatch: 'full'},
+];
 
 @NgModule({
-  imports: [RouterModule.forRoot(routes)],
+  imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: "reload", enableTracing: true})],
   exports: [RouterModule]
 })
 export class AppRoutingModule {

+ 12 - 1
src/app/src/app/app.component.html

@@ -1 +1,12 @@
-<router-outlet></router-outlet>
+<mat-toolbar class="mat-elevation-z6">
+  <mat-toolbar-row fxLayout="row" fxLayoutAlign="space-between center">
+    <span><span [routerLink]="['/']" class="href"><img alt="logo" src="../assets/favicon-16x16.png">&nbsp;СВОБОДНЫЕ ДЕНЬГИ</span></span>
+    <div [ngSwitch]="hasAuth()">
+      <button *ngSwitchCase="true" mat-button>ВЫЙТИ</button>
+      <button *ngSwitchDefault [routerLink]="['/login']" mat-button>ВОЙТИ</button>
+    </div>
+  </mat-toolbar-row>
+</mat-toolbar>
+<div class="main">
+  <router-outlet></router-outlet>
+</div>

+ 5 - 1
src/app/src/app/app.component.ts

@@ -6,5 +6,9 @@ import {Component} from '@angular/core';
   styleUrls: ['./app.component.scss']
 })
 export class AppComponent {
-  title = 'frei-app';
+
+  hasAuth(): boolean {
+    return false
+  }
+
 }

+ 7 - 0
src/app/src/app/app.index.component.html

@@ -0,0 +1,7 @@
+<div>
+  <mat-card fxFlex="33">
+    <mat-card-content>
+      Только те деньги, которые приходят в негодность, как вчерашние газеты, как прошлогодний картофель, как железяка, пролежавшая в земле сто лет, и могут быть настоящими деньгами, т. е. инструментом для обмена тех же газет, картофеля, железа и т. д. Потому что такие деньги никто не будет отличать от собственно товаров, которые потребляет человек, никто их не будет никоим образом отличать, как нечто лучшее. Никто: ни покупатель, ни продавец. И тогда, и только тогда деньги станут тем, что они есть в их самом чистом виде: средством обмена, помощником при обмене товарами.
+    </mat-card-content>
+  </mat-card>
+</div>

+ 9 - 0
src/app/src/app/app.index.component.ts

@@ -0,0 +1,9 @@
+import {Component} from "@angular/core";
+
+@Component({
+  selector: 'app-index',
+  templateUrl: 'app.index.component.html'
+})
+export class AppIndexComponent {
+
+}

+ 13 - 0
src/app/src/app/app.login.component.html

@@ -0,0 +1,13 @@
+<div>
+  <p>Всё просто, для регистрации введите проверочную строку <strong>{{(qr | async).key}}</strong> или отсканируйте qr-код с помощью приложения Authenticator, затем введите шестизначный код из приложения и можно начинать.</p>
+  <p>Или, если вы уже зарегистрированы, просто введите шестизначный код.</p>
+  <div><img alt="your qr-code here" src="{{(qr | async).img}}"></div>
+  <div fxLayout="row" fxLayoutAlign="start center">
+    <mat-form-field>
+      <mat-label>Проверочный код</mat-label>
+      <input (ngModelChange)="checkCode($event)" [ngModel]="code" matInput maxlength="6" minlength="6" pattern="^[0-9]+$" placeholder="123456" type="text"/>
+    </mat-form-field>
+    <mat-icon *ngIf="valid === 1">done</mat-icon>
+    <mat-icon *ngIf="valid === 0">not_interested</mat-icon>
+  </div>
+</div>

+ 47 - 0
src/app/src/app/app.login.component.ts

@@ -0,0 +1,47 @@
+import {Component, OnInit} from "@angular/core";
+import {HttpClient} from "@angular/common/http";
+import {ReplaySubject} from "rxjs";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {PersonService} from "./person/person.service";
+
+@Component({
+  selector: 'app-login',
+  templateUrl: 'app.login.component.html',
+})
+export class AppLoginComponent implements OnInit {
+
+  public qr: ReplaySubject<QrResponse>
+  public code: string
+  public valid: number = null;
+
+  constructor(private personService: PersonService, private httpClient: HttpClient, private snackBar: MatSnackBar) {
+    this.qr = new ReplaySubject<QrResponse>();
+  }
+
+  ngOnInit(): void {
+    this.personService.getCurrentPerson().subscribe(person => this.httpClient.get("/api/new/person/qr", {params: {siteId: person.siteId}}).subscribe((res: QrResponse) => this.qr.next(res), error => this.snackBar.open("Ошибка получения данных")));
+  }
+
+  checkCode(c: String) {
+    if (c && c.length == 6) {
+      this.qr.subscribe(qrRes => {
+        this.personService.getCurrentPerson().subscribe(person => {
+          this.httpClient.post("/api/new/person/check6", {siteId: person.siteId, code: c, key: qrRes.key}).subscribe((res: QrSixResponse) => {
+            this.valid = res.ok ? 1 : 0;
+          })
+        })
+      })
+    }
+    this.valid = null
+  }
+}
+
+export class QrSixResponse {
+  public ok: boolean
+  public register: boolean
+}
+
+export class QrResponse {
+  public key: string
+  public img: string
+}

+ 35 - 4
src/app/src/app/app.module.ts

@@ -1,20 +1,51 @@
 import {BrowserModule} from '@angular/platform-browser';
-import {NgModule} from '@angular/core';
+import {LOCALE_ID, NgModule} from '@angular/core';
 
 import {AppRoutingModule} from './app-routing.module';
 import {AppComponent} from './app.component';
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {MatToolbarModule} from "@angular/material/toolbar";
+import {FlexLayoutModule} from "@angular/flex-layout";
+import {MatButtonModule} from "@angular/material/button";
+import {AppLoginComponent} from "./app.login.component";
+import {AppIndexComponent} from "./app.index.component";
+import {MatCardModule} from "@angular/material/card";
+import {MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule} from "@angular/material/snack-bar";
+import {registerLocaleData} from "@angular/common";
+import localeRu from '@angular/common/locales/ru'
+import {HttpClientModule} from "@angular/common/http";
+import {MatFormFieldModule} from "@angular/material/form-field";
+import {MatInputModule} from "@angular/material/input";
+import {FormsModule} from "@angular/forms";
+import {MatIconModule} from "@angular/material/icon";
+import {PersonService} from "./person/person.service";
+
+registerLocaleData(localeRu);
 
 @NgModule({
   declarations: [
-    AppComponent
+    AppComponent, AppLoginComponent, AppIndexComponent
   ],
   imports: [
     BrowserModule,
     AppRoutingModule,
-    BrowserAnimationsModule
+    BrowserAnimationsModule,
+    MatToolbarModule,
+    FormsModule,
+    FlexLayoutModule,
+    MatButtonModule,
+    MatCardModule,
+    HttpClientModule,
+    MatSnackBarModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatIconModule
+  ],
+  providers: [
+    PersonService,
+    {provide: LOCALE_ID, useValue: "ru-RU"},
+    {provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: {duration: 2500}}
   ],
-  providers: [],
   bootstrap: [AppComponent]
 })
 export class AppModule {

+ 31 - 0
src/app/src/app/person/person.service.ts

@@ -0,0 +1,31 @@
+import {Injectable} from "@angular/core";
+import {Observable, ReplaySubject} from "rxjs";
+import {v4 as uuidv4} from 'uuid';
+
+@Injectable({providedIn: "root"})
+export class PersonService {
+
+  private siteId: string
+
+  constructor() {
+    if (!(this.siteId = localStorage.siteId)) {
+      localStorage.siteId = this.siteId = uuidv4()
+    }
+  }
+
+  getCurrentPerson(): Observable<Person> {
+    let ret = new ReplaySubject<Person>();
+    setTimeout(() => {
+      let p = new Person();
+      p.siteId = this.siteId
+      ret.next(p)
+    })
+    return ret;
+  }
+
+}
+
+export class Person {
+  public siteId: string
+  public auth: any
+}

BIN
src/app/src/assets/android-chrome-192x192.png


BIN
src/app/src/assets/android-chrome-512x512.png


BIN
src/app/src/assets/apple-touch-icon.png


BIN
src/app/src/assets/favicon-16x16.png


BIN
src/app/src/assets/favicon-32x32.png


BIN
src/app/src/assets/favicon.ico


+ 1 - 0
src/app/src/assets/frei.webmanifest

@@ -0,0 +1 @@
+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

BIN
src/app/src/favicon.ico


+ 3 - 2
src/app/src/index.html

@@ -2,10 +2,11 @@
 <html lang="en">
 <head>
   <meta charset="utf-8">
-  <title>FreiApp</title>
+  <title>FREIGELD</title>
   <base href="/">
   <meta content="width=device-width, initial-scale=1" name="viewport">
-  <link href="favicon.ico" rel="icon" type="image/x-icon">
+  <link href="assets/favicon.ico" rel="icon" type="image/x-icon">
+  <link href="assets/frei.webmanifest" rel="manifest">
   <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
   <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 </head>

+ 9 - 0
src/app/src/styles.scss

@@ -8,3 +8,12 @@ body {
   margin: 0;
   font-family: Roboto, "Helvetica Neue", sans-serif;
 }
+
+.href {
+  cursor: pointer;
+  outline: none;
+}
+
+.main {
+  margin: 10px;
+}

+ 2 - 1
src/app/tsconfig.json

@@ -25,6 +25,7 @@
   },
   "angularCompilerOptions": {
     "fullTemplateTypeCheck": false,
-    "strictInjectionParameters": false
+    "strictInjectionParameters": false,
+    "enableIvy": false
   }
 }

+ 0 - 148
src/app/tslint.json

@@ -1,148 +0,0 @@
-{
-  "extends": "tslint:recommended",
-  "rules": {
-    "align": {
-      "options": [
-        "parameters",
-        "statements"
-      ]
-    },
-    "array-type": false,
-    "arrow-return-shorthand": true,
-    "curly": true,
-    "deprecation": {
-      "severity": "warning"
-    },
-    "component-class-suffix": true,
-    "contextual-lifecycle": true,
-    "directive-class-suffix": true,
-    "directive-selector": [
-      true,
-      "attribute",
-      "app",
-      "camelCase"
-    ],
-    "component-selector": [
-      true,
-      "element",
-      "app",
-      "kebab-case"
-    ],
-    "eofline": true,
-    "import-blacklist": [
-      true,
-      "rxjs/Rx"
-    ],
-    "import-spacing": true,
-    "indent": {
-      "options": [
-        "spaces"
-      ]
-    },
-    "max-classes-per-file": false,
-    "max-line-length": [
-      true,
-      140
-    ],
-    "member-ordering": [
-      true,
-      {
-        "order": [
-          "static-field",
-          "instance-field",
-          "static-method",
-          "instance-method"
-        ]
-      }
-    ],
-    "no-console": [
-      true,
-      "debug",
-      "info",
-      "time",
-      "timeEnd",
-      "trace"
-    ],
-    "no-empty": false,
-    "no-inferrable-types": [
-      true,
-      "ignore-params"
-    ],
-    "no-non-null-assertion": true,
-    "no-redundant-jsdoc": true,
-    "no-switch-case-fall-through": true,
-    "no-var-requires": false,
-    "object-literal-key-quotes": [
-      true,
-      "as-needed"
-    ],
-    "quotemark": [
-      true,
-      "single"
-    ],
-    "semicolon": {
-      "options": [
-        "always"
-      ]
-    },
-    "space-before-function-paren": {
-      "options": {
-        "anonymous": "never",
-        "asyncArrow": "always",
-        "constructor": "never",
-        "method": "never",
-        "named": "never"
-      }
-    },
-    "typedef-whitespace": {
-      "options": [
-        {
-          "call-signature": "nospace",
-          "index-signature": "nospace",
-          "parameter": "nospace",
-          "property-declaration": "nospace",
-          "variable-declaration": "nospace"
-        },
-        {
-          "call-signature": "onespace",
-          "index-signature": "onespace",
-          "parameter": "onespace",
-          "property-declaration": "onespace",
-          "variable-declaration": "onespace"
-        }
-      ]
-    },
-    "variable-name": {
-      "options": [
-        "ban-keywords",
-        "check-format",
-        "allow-pascal-case"
-      ]
-    },
-    "whitespace": {
-      "options": [
-        "check-branch",
-        "check-decl",
-        "check-operator",
-        "check-separator",
-        "check-type",
-        "check-typecast"
-      ]
-    },
-    "no-conflicting-lifecycle": true,
-    "no-host-metadata-property": true,
-    "no-input-rename": true,
-    "no-inputs-metadata-property": true,
-    "no-output-native": true,
-    "no-output-on-prefix": true,
-    "no-output-rename": true,
-    "no-outputs-metadata-property": true,
-    "template-banana-in-box": true,
-    "template-no-negated-async": true,
-    "use-lifecycle-interface": true,
-    "use-pipe-transform-interface": true
-  },
-  "rulesDirectory": [
-    "codelyzer"
-  ]
-}

+ 5 - 0
src/app/yarn.lock

@@ -7554,6 +7554,11 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
   integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 
+uuid@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c"
+  integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
+
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"

+ 1 - 1
src/main/java/inn/ocsf/bee/freigeld/core/cl/DryRunnable.java → src/main/java/inn/ocsf/bee/freigeld/cl/DryRunnable.java

@@ -1,4 +1,4 @@
-package inn.ocsf.bee.freigeld.core.cl;
+package inn.ocsf.bee.freigeld.cl;
 
 import com.aparapi.Kernel;
 

+ 1 - 1
src/main/java/inn/ocsf/bee/freigeld/core/cl/KernelTri.java → src/main/java/inn/ocsf/bee/freigeld/cl/KernelTri.java

@@ -1,4 +1,4 @@
-package inn.ocsf.bee.freigeld.core.cl;
+package inn.ocsf.bee.freigeld.cl;
 
 import com.aparapi.Kernel;
 

+ 1 - 1
src/main/java/inn/ocsf/bee/freigeld/core/cl/Pt.java → src/main/java/inn/ocsf/bee/freigeld/cl/Pt.java

@@ -1,4 +1,4 @@
-package inn.ocsf.bee.freigeld.core.cl;
+package inn.ocsf.bee.freigeld.cl;
 
 final class Pt {
     private int x, y;

+ 1 - 1
src/main/java/inn/ocsf/bee/freigeld/core/cl/Sample0.java → src/main/java/inn/ocsf/bee/freigeld/cl/Sample0.java

@@ -1,4 +1,4 @@
-package inn.ocsf.bee.freigeld.core.cl;
+package inn.ocsf.bee.freigeld.cl;
 
 import com.aparapi.Kernel;
 import com.aparapi.Range;

+ 1 - 1
src/main/java/inn/ocsf/bee/freigeld/core/cl/Sample1.java → src/main/java/inn/ocsf/bee/freigeld/cl/Sample1.java

@@ -1,4 +1,4 @@
-package inn.ocsf.bee.freigeld.core.cl;
+package inn.ocsf.bee.freigeld.cl;
 
 import com.aparapi.Kernel;
 import com.aparapi.Range;

+ 4 - 0
src/main/java/inn/ocsf/bee/freigeld/cl/Sample3.java

@@ -0,0 +1,4 @@
+package inn.ocsf.bee.freigeld.cl;
+
+public class Sample3 {
+}

+ 0 - 4
src/main/java/inn/ocsf/bee/freigeld/core/cl/Sample3.java

@@ -1,4 +0,0 @@
-package inn.ocsf.bee.freigeld.core.cl;
-
-public class Sample3 {
-}

+ 1 - 1
src/main/kotlin/inn/ocsf/bee/freigeld/core/FreiApp.kt → src/main/kotlin/inn/ocsf/bee/freigeld/FreiApp.kt

@@ -1,4 +1,4 @@
-package inn.ocsf.bee.freigeld.core
+package inn.ocsf.bee.freigeld
 
 import inn.ocsf.bee.freigeld.core.demo.DemoInMem
 import org.springframework.boot.SpringApplication

+ 12 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/data/GlobalEmitterImpl.kt

@@ -7,6 +7,7 @@ import inn.ocsf.bee.freigeld.core.model.data.CoinData
 import inn.ocsf.bee.freigeld.core.model.data.CoinStatus
 import inn.ocsf.bee.freigeld.core.model.data.QCoinData.coinData
 import inn.ocsf.bee.freigeld.core.repo.CoinRepository
+import inn.ocsf.bee.freigeld.core.service.TicketService
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
@@ -24,9 +25,16 @@ import kotlin.concurrent.withLock
 @Service
 class GlobalEmitterImpl : GlobalEmitter {
 
+    companion object {
+        val emitterTicketChannel = GlobalEmitter::class.java.name
+    }
+
     @Inject
     private lateinit var coinRepo: CoinRepository
 
+    @Inject
+    private lateinit var ticketService: TicketService
+
     private val globalId = UUID.fromString("a671cdca-782a-4caf-8aad-056f6b62d822")
 
     private val listeners = mutableListOf<Consumer<EmitterEvent>>()
@@ -38,6 +46,10 @@ class GlobalEmitterImpl : GlobalEmitter {
     @PostConstruct
     fun init() {
         //coinRepo.deleteAll()
+        ticketService.listen(emitterTicketChannel) {
+            calc()
+            true
+        }
     }
 
     private fun computeIfAbsent(future: Coin? = null, fn: () -> CoinData): Coin {

+ 9 - 7
src/main/kotlin/inn/ocsf/bee/freigeld/core/demo/DemoInMem.kt

@@ -1,14 +1,13 @@
 package inn.ocsf.bee.freigeld.core.demo
 
-import inn.ocsf.bee.freigeld.core.model.BankAccount
-import inn.ocsf.bee.freigeld.core.model.CentralBank
-import inn.ocsf.bee.freigeld.core.model.GlobalEmitter
-import inn.ocsf.bee.freigeld.core.model.GlobalWorld
+import inn.ocsf.bee.freigeld.core.model.*
+import inn.ocsf.bee.freigeld.core.service.TicketService
 import org.slf4j.LoggerFactory
 import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.scheduling.annotation.Scheduled
 import org.springframework.stereotype.Service
+import java.util.*
 import javax.annotation.PostConstruct
+import javax.inject.Inject
 
 @Service
 class DemoInMem {
@@ -24,6 +23,9 @@ class DemoInMem {
 
     private val log = LoggerFactory.getLogger(javaClass)
 
+    @Inject
+    private lateinit var ticketService: TicketService
+
     @PostConstruct
     fun init() {
         /*emitter.emit(10000, CoinValue.one)
@@ -78,9 +80,9 @@ class DemoInMem {
         log.info("tick")
     }
 
-    @Scheduled(initialDelay = 60 * 1000L, fixedDelay = 60 * 1000L)
+    //@Scheduled(initialDelay = 60 * 1000L, fixedDelay = 60 * 1000L)
     fun calc() {
-        emitter.calc()
+        ticketService.offerLast("${GlobalEmitter::class.java}", object : Ticket(UUID.randomUUID()) {})
         log.info("calc")
     }
 }

+ 7 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/model/PersonIdentityString.kt → src/main/kotlin/inn/ocsf/bee/freigeld/core/model/PersonIdentityTypes.kt

@@ -9,3 +9,10 @@ data class PersonIdentityFullName(val fullName: String) : PersonIdentity {
     }
 }
 
+data class PersonIdentitySecret(val secret: String) : PersonIdentity {
+
+    override fun getKey(): String {
+        return secret
+    }
+
+}

+ 96 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/serve/rest/PersonController.kt

@@ -0,0 +1,96 @@
+package inn.ocsf.bee.freigeld.serve.rest
+
+import com.oblac.nomen.Nomen
+import dev.samstevens.totp.code.CodeGenerator
+import dev.samstevens.totp.code.DefaultCodeGenerator
+import dev.samstevens.totp.code.DefaultCodeVerifier
+import dev.samstevens.totp.code.HashingAlgorithm
+import dev.samstevens.totp.qr.QrData
+import dev.samstevens.totp.qr.ZxingPngQrGenerator
+import dev.samstevens.totp.secret.DefaultSecretGenerator
+import dev.samstevens.totp.time.SystemTimeProvider
+import dev.samstevens.totp.time.TimeProvider
+import dev.samstevens.totp.util.Utils.getDataUriForImage
+import inn.ocsf.bee.freigeld.core.model.GlobalWorld
+import inn.ocsf.bee.freigeld.core.model.PersonIdentityFullName
+import inn.ocsf.bee.freigeld.core.model.PersonIdentitySecret
+import inn.ocsf.bee.freigeld.core.model.data.PersonData
+import inn.ocsf.bee.freigeld.core.repo.PersonRepository
+import inn.ocsf.bee.freigeld.utils.KeyValueBucket.personToSite
+import inn.ocsf.bee.freigeld.utils.KeyValueBucket.secretToSite
+import inn.ocsf.bee.freigeld.utils.KeyValueStorage
+import org.apache.commons.lang3.RandomStringUtils
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.*
+import java.util.*
+import javax.inject.Inject
+
+
+@RestController
+class PersonController {
+
+    @Inject
+    private lateinit var storage: KeyValueStorage
+
+    @Inject
+    private lateinit var personRepo: PersonRepository
+
+    @Inject
+    private lateinit var world: GlobalWorld
+
+    @RequestMapping("api/new/person/qr", method = [RequestMethod.GET])
+    fun newqr(@RequestParam("siteId") siteId: UUID): ResponseEntity<QrDataResponse> {
+        val secretGenerator = DefaultSecretGenerator()
+        val secret = secretGenerator.generate()
+
+        storage.put(secretToSite, secret, siteId)
+
+        val data = QrData.Builder().secret(secret).algorithm(HashingAlgorithm.SHA1).label("personal wallet").issuer("FreiGeld").digits(6).period(30).build()
+
+        val generator = ZxingPngQrGenerator()
+        val imageData = generator.generate(data)
+        val mimeType = generator.imageMimeType
+        val dataUri = getDataUriForImage(imageData, mimeType)
+
+        /*
+        val recoveryCodeGenerator = RecoveryCodeGenerator()
+        val codes = recoveryCodeGenerator.generateCodes(16)
+        */
+        return ResponseEntity.ok(QrDataResponse(secret, dataUri))
+    }
+
+    @RequestMapping("api/new/person/check6", method = [RequestMethod.POST])
+    fun checkqr(@RequestBody req: QrSixRequest): ResponseEntity<QrSixResponse> {
+        val timeProvider: TimeProvider = SystemTimeProvider()
+        val codeGenerator: CodeGenerator = DefaultCodeGenerator(HashingAlgorithm.SHA1)
+        val verifier = DefaultCodeVerifier(codeGenerator, timeProvider)
+        verifier.setTimePeriod(30)
+        verifier.setAllowedTimePeriodDiscrepancy(4)
+
+        var register = false
+
+        val thisPerson = if (verifier.isValidCode(req.key, req.code)) { //new registration
+            val person = PersonData.NaturalPersonImpl("${Nomen.randomName()}_${RandomStringUtils.randomAlphanumeric(5)}")
+            world.addPerson(person)
+            world.setPersonIdentity(person, PersonIdentityFullName(person.fullName))
+            world.setPersonIdentity(person, PersonIdentitySecret(req.key))
+            storage.put(personToSite, person.id, req.siteId)
+            register = true
+            person
+        } else {
+            storage.getByValue<UUID>(personToSite, req.siteId).map { id -> personRepo.findOneByPersonId(id).orElseThrow { throw RuntimeException("strange person ${id} not found") } }.filterNotNull().filter {
+                val secret = it.identities.values.filter { it is PersonIdentitySecret }.first()
+                verifier.isValidCode(secret.key, req.code)
+            }.map { it.person }.firstOrNull()
+        }
+
+        return ResponseEntity.ok(QrSixResponse(thisPerson != null, register))
+    }
+
+}
+
+data class QrDataResponse(val key: String, val img: String)
+
+data class QrSixRequest(val siteId: UUID, val code: String, val key: String)
+
+data class QrSixResponse(val ok: Boolean, val register: Boolean)

+ 59 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/utils/KeyValueStorage.kt

@@ -0,0 +1,59 @@
+package inn.ocsf.bee.freigeld.utils
+
+import org.bson.types.ObjectId
+import org.springframework.data.annotation.Id
+import org.springframework.data.mongodb.core.mapping.Document
+import org.springframework.data.mongodb.repository.MongoRepository
+import org.springframework.stereotype.Service
+import java.util.*
+import javax.inject.Inject
+
+@Service
+class KeyValueStorage {
+
+    @Inject
+    private lateinit var keyValueRepo: KeyValueRepository
+
+    fun <T> get(bucket: KeyValueBucket, key: Any): T? {
+        return keyValueRepo.findOneByBucketAndKey(bucket, key).map { it.value as T }.orElse(null)
+    }
+
+    fun put(bucket: KeyValueBucket, key: Any, value: Any) {
+        keyValueRepo.save(KeyValueObject(bucket, key, value))
+    }
+
+    fun <T> getByValue(bucket: KeyValueBucket, value: Any): List<T> {
+        return keyValueRepo.findAllByBucketAndValue(bucket, value).map { it.key as T }
+    }
+
+    fun <K, V> getAll(bucket: KeyValueBucket): List<Pair<K, V>> {
+        return keyValueRepo.findAllByBucket(bucket).map { it.key as K to it.value as V }
+    }
+}
+
+enum class KeyValueBucket(val bucket: String) {
+    personToSite("person-to-site"),
+    secretToSite("secret-to-site");
+}
+
+interface KeyValueRepository : MongoRepository<KeyValueObject, ObjectId> {
+    fun findOneByBucketAndKey(bucket: KeyValueBucket, key: Any): Optional<KeyValueObject>
+    fun findAllByBucketAndValue(bucket: KeyValueBucket, value: Any): Collection<KeyValueObject>
+    fun findAllByBucket(bucket: KeyValueBucket): Collection<KeyValueObject>
+}
+
+@Document("kv-store")
+class KeyValueObject() {
+
+    @Id
+    var id: ObjectId? = null
+    var bucket: KeyValueBucket? = null
+    var key: Any? = null
+    var value: Any? = null
+
+    constructor(bucket: KeyValueBucket, key: Any, value: Any) : this() {
+        this.bucket = bucket
+        this.key = key
+        this.value = value
+    }
+}

+ 1 - 1
src/main/resources/application.yaml

@@ -1,3 +1,3 @@
 server:
   servlet:
-    context-path: /frei
+    context-path: /