From 12ce5c57570cf0ac9f123613c72be44d0878c6f4 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Thu, 6 Aug 2020 23:37:58 +0800 Subject: [PATCH 01/31] WIP: implement numbers storage publisher pages. --- src/app/app-routing.module.ts | 6 ++++ src/app/app.component.ts | 4 +-- .../login/login-routing.module.ts | 14 ++++++++ .../numbers-storage/login/login.module.ts | 19 +++++++++++ .../numbers-storage/login/login.page.html | 32 +++++++++++++++++++ .../numbers-storage/login/login.page.scss | 3 ++ .../numbers-storage/login/login.page.spec.ts | 24 ++++++++++++++ .../numbers-storage/login/login.page.ts | 23 +++++++++++++ .../numbers-storage-routing.module.ts | 20 ++++++++++++ .../numbers-storage/numbers-storage.module.ts | 19 +++++++++++ .../numbers-storage/numbers-storage.page.html | 16 ++++++++++ .../numbers-storage/numbers-storage.page.scss | 3 ++ .../numbers-storage.page.spec.ts | 24 ++++++++++++++ .../numbers-storage/numbers-storage.page.ts | 9 ++++++ .../sign-up/sign-up-routing.module.ts | 14 ++++++++ .../numbers-storage/sign-up/sign-up.module.ts | 19 +++++++++++ .../numbers-storage/sign-up/sign-up.page.html | 32 +++++++++++++++++++ .../numbers-storage/sign-up/sign-up.page.scss | 0 .../sign-up/sign-up.page.spec.ts | 24 ++++++++++++++ .../numbers-storage/sign-up/sign-up.page.ts | 23 +++++++++++++ .../pages/settings/settings-routing.module.ts | 15 ++++----- src/app/pages/settings/settings.page.html | 5 +++ .../numbers-storage/numbers-storage.spec.ts | 1 + .../numbers-storage.ts} | 4 +-- .../sample-publisher.service.spec.ts | 1 - src/assets/i18n/en-us.json | 5 +++ src/assets/image/n.svg | 11 +++++++ src/assets/image/numbers.svg | 12 +++++++ src/assets/shapes.svg | 1 - 29 files changed, 368 insertions(+), 15 deletions(-) create mode 100644 src/app/pages/publishers/numbers-storage/login/login-routing.module.ts create mode 100644 src/app/pages/publishers/numbers-storage/login/login.module.ts create mode 100644 src/app/pages/publishers/numbers-storage/login/login.page.html create mode 100644 src/app/pages/publishers/numbers-storage/login/login.page.scss create mode 100644 src/app/pages/publishers/numbers-storage/login/login.page.spec.ts create mode 100644 src/app/pages/publishers/numbers-storage/login/login.page.ts create mode 100644 src/app/pages/publishers/numbers-storage/numbers-storage-routing.module.ts create mode 100644 src/app/pages/publishers/numbers-storage/numbers-storage.module.ts create mode 100644 src/app/pages/publishers/numbers-storage/numbers-storage.page.html create mode 100644 src/app/pages/publishers/numbers-storage/numbers-storage.page.scss create mode 100644 src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts create mode 100644 src/app/pages/publishers/numbers-storage/numbers-storage.page.ts create mode 100644 src/app/pages/publishers/numbers-storage/sign-up/sign-up-routing.module.ts create mode 100644 src/app/pages/publishers/numbers-storage/sign-up/sign-up.module.ts create mode 100644 src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html create mode 100644 src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss create mode 100644 src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts create mode 100644 src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts create mode 100644 src/app/services/publisher/numbers-storage/numbers-storage.spec.ts rename src/app/services/publisher/{sample-publisher/sample-publisher.ts => numbers-storage/numbers-storage.ts} (82%) delete mode 100644 src/app/services/publisher/sample-publisher/sample-publisher.service.spec.ts create mode 100644 src/assets/image/n.svg create mode 100644 src/assets/image/numbers.svg delete mode 100644 src/assets/shapes.svg diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 2f0641283..65c2a0d42 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -14,6 +14,12 @@ const routes: Routes = [{ }, { path: 'proof', loadChildren: () => import('./pages/proof/proof.module').then(m => m.ProofPageModule) +}, { + path: 'publishers', + redirectTo: 'settings' +}, { + path: 'publishers/numbers-storage', + loadChildren: () => import('./pages/publishers/numbers-storage/numbers-storage.module').then(m => m.NumbersStoragePageModule) }]; @NgModule({ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f6835e406..555d472ac 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -10,8 +10,8 @@ import { InformationRepository } from './services/data/information/information-r import { SignatureRepository } from './services/data/signature/signature-repository.service'; import { LanguageService } from './services/language/language.service'; import { NotificationService } from './services/notification/notification.service'; +import { NumbersStoragePublisher } from './services/publisher/numbers-storage/numbers-storage'; import { PublishersAlert } from './services/publisher/publishers-alert/publishers-alert.service'; -import { SamplePublisher } from './services/publisher/sample-publisher/sample-publisher'; import { SerializationService } from './services/serialization/serialization.service'; const { SplashScreen } = Plugins; @@ -58,7 +58,7 @@ export class AppComponent { initializePublisher() { this.publishersAlert.addPublisher( - new SamplePublisher(this.translocoService, this.notificationService) + new NumbersStoragePublisher(this.translocoService, this.notificationService) ); } } diff --git a/src/app/pages/publishers/numbers-storage/login/login-routing.module.ts b/src/app/pages/publishers/numbers-storage/login/login-routing.module.ts new file mode 100644 index 000000000..698f28e30 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/login/login-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { LoginPage } from './login.page'; + +const routes: Routes = [{ + path: '', + component: LoginPage +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class LoginPageRoutingModule { } diff --git a/src/app/pages/publishers/numbers-storage/login/login.module.ts b/src/app/pages/publishers/numbers-storage/login/login.module.ts new file mode 100644 index 000000000..442754956 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/login/login.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; +import { LoginPageRoutingModule } from './login-routing.module'; +import { LoginPage } from './login.page'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + LoginPageRoutingModule, + TranslocoModule + ], + declarations: [LoginPage] +}) +export class LoginPageModule { } diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.html b/src/app/pages/publishers/numbers-storage/login/login.page.html new file mode 100644 index 000000000..80d68ab1d --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/login/login.page.html @@ -0,0 +1,32 @@ + + + + + + + + + {{ t('login') }} + + + + + +
+ + + + {{ t('email') }} + + + + + {{ t('password') }} + + + + {{ t('login') }} + +
+ {{ t('signUp') }} +
\ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.scss b/src/app/pages/publishers/numbers-storage/login/login.page.scss new file mode 100644 index 000000000..98582c5a0 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/login/login.page.scss @@ -0,0 +1,3 @@ +.numbers-logo { + margin: 10vh 20vw; +} diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts b/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts new file mode 100644 index 000000000..4694aa6ac --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { LoginPage } from './login.page'; + +describe('LoginPage', () => { + let component: LoginPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LoginPage], + imports: [IonicModule.forRoot(), ReactiveFormsModule] + }).compileComponents(); + + fixture = TestBed.createComponent(LoginPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.ts b/src/app/pages/publishers/numbers-storage/login/login.page.ts new file mode 100644 index 000000000..03cf7706c --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/login/login.page.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'app-login', + templateUrl: './login.page.html', + styleUrls: ['./login.page.scss'], +}) +export class LoginPage { + + loginForm = this.formBuilder.group({ + email: ['', [Validators.email, Validators.required]], + password: ['', Validators.required] + }); + + constructor( + private readonly formBuilder: FormBuilder + ) { } + + login() { + + } +} diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage-routing.module.ts b/src/app/pages/publishers/numbers-storage/numbers-storage-routing.module.ts new file mode 100644 index 000000000..6fddc848a --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/numbers-storage-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { NumbersStoragePage } from './numbers-storage.page'; + +const routes: Routes = [{ + path: '', + component: NumbersStoragePage +}, { + path: 'login', + loadChildren: () => import('./login/login.module').then(m => m.LoginPageModule) +}, { + path: 'sign-up', + loadChildren: () => import('./sign-up/sign-up.module').then(m => m.SignUpPageModule) +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class NumbersStoragePageRoutingModule { } diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.module.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.module.ts new file mode 100644 index 000000000..47c10ad95 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; +import { NumbersStoragePageRoutingModule } from './numbers-storage-routing.module'; +import { NumbersStoragePage } from './numbers-storage.page'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + NumbersStoragePageRoutingModule, + TranslocoModule + ], + declarations: [NumbersStoragePage] +}) +export class NumbersStoragePageModule { } diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html new file mode 100644 index 000000000..94ae9134c --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html @@ -0,0 +1,16 @@ + + + + + + + + Numbers Storage + + + + + + go to login + go to sign up + \ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.scss b/src/app/pages/publishers/numbers-storage/numbers-storage.page.scss new file mode 100644 index 000000000..98582c5a0 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.scss @@ -0,0 +1,3 @@ +.numbers-logo { + margin: 10vh 20vw; +} diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts new file mode 100644 index 000000000..798478fd7 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { IonicModule } from '@ionic/angular'; +import { NumbersStoragePage } from './numbers-storage.page'; + +describe('NumbersStoragePage', () => { + let component: NumbersStoragePage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [NumbersStoragePage], + imports: [IonicModule.forRoot(), RouterTestingModule] + }).compileComponents(); + + fixture = TestBed.createComponent(NumbersStoragePage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts new file mode 100644 index 000000000..bd2d07ac2 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-numbers-storage', + templateUrl: './numbers-storage.page.html', + styleUrls: ['./numbers-storage.page.scss'], +}) +export class NumbersStoragePage { +} diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up-routing.module.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up-routing.module.ts new file mode 100644 index 000000000..0da3a1d06 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { SignUpPage } from './sign-up.page'; + +const routes: Routes = [{ + path: '', + component: SignUpPage +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SignUpPageRoutingModule { } diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.module.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.module.ts new file mode 100644 index 000000000..6fe603020 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; +import { SignUpPageRoutingModule } from './sign-up-routing.module'; +import { SignUpPage } from './sign-up.page'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + SignUpPageRoutingModule, + TranslocoModule + ], + declarations: [SignUpPage] +}) +export class SignUpPageModule { } diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html new file mode 100644 index 000000000..f4ab40b9b --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html @@ -0,0 +1,32 @@ + + + + + + + + + {{ t('signUp') }} + + + + + +
+ + + + {{ t('email') }} + + + + + {{ t('password') }} + + + + {{ t('signUp') }} + +
+ {{ t('signUp') }} +
\ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts new file mode 100644 index 000000000..7691ecb52 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { SignUpPage } from './sign-up.page'; + +describe('SignUpPage', () => { + let component: SignUpPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SignUpPage], + imports: [IonicModule.forRoot(), ReactiveFormsModule] + }).compileComponents(); + + fixture = TestBed.createComponent(SignUpPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts new file mode 100644 index 000000000..0b15ee7a2 --- /dev/null +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'app-sign-up', + templateUrl: './sign-up.page.html', + styleUrls: ['./sign-up.page.scss'], +}) +export class SignUpPage { + + signUpForm = this.formBuilder.group({ + email: ['', [Validators.email, Validators.required]], + password: ['', Validators.required] + }); + + constructor( + private readonly formBuilder: FormBuilder + ) { } + + signUp() { + + } +} diff --git a/src/app/pages/settings/settings-routing.module.ts b/src/app/pages/settings/settings-routing.module.ts index 59decc029..16c698665 100644 --- a/src/app/pages/settings/settings-routing.module.ts +++ b/src/app/pages/settings/settings-routing.module.ts @@ -1,17 +1,14 @@ import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; - +import { RouterModule, Routes } from '@angular/router'; import { SettingsPage } from './settings.page'; -const routes: Routes = [ - { - path: '', - component: SettingsPage - } -]; +const routes: Routes = [{ + path: '', + component: SettingsPage +}]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class SettingsPageRoutingModule {} +export class SettingsPageRoutingModule { } diff --git a/src/app/pages/settings/settings.page.html b/src/app/pages/settings/settings.page.html index b54399e8f..e2b014e88 100644 --- a/src/app/pages/settings/settings.page.html +++ b/src/app/pages/settings/settings.page.html @@ -48,6 +48,11 @@

{{ t('privateKey') }}

{{ privateKey$ | async }}

+ {{ t('publisher')}} + + + Numbers Storage + {{ t('about') }} diff --git a/src/app/services/publisher/numbers-storage/numbers-storage.spec.ts b/src/app/services/publisher/numbers-storage/numbers-storage.spec.ts new file mode 100644 index 000000000..9e2858e8b --- /dev/null +++ b/src/app/services/publisher/numbers-storage/numbers-storage.spec.ts @@ -0,0 +1 @@ +describe('NumbersStorage', () => { }); diff --git a/src/app/services/publisher/sample-publisher/sample-publisher.ts b/src/app/services/publisher/numbers-storage/numbers-storage.ts similarity index 82% rename from src/app/services/publisher/sample-publisher/sample-publisher.ts rename to src/app/services/publisher/numbers-storage/numbers-storage.ts index 6bd38935d..259a352fe 100644 --- a/src/app/services/publisher/sample-publisher/sample-publisher.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage.ts @@ -3,9 +3,9 @@ import { delay, tap } from 'rxjs/operators'; import { Proof } from '../../data/proof/proof'; import { Publisher } from '../publisher'; -export class SamplePublisher extends Publisher { +export class NumbersStoragePublisher extends Publisher { - readonly name = 'Sample Publisher'; + readonly name = 'Numbers Storage'; run$(proof: Proof): Observable { return of(void 0).pipe( diff --git a/src/app/services/publisher/sample-publisher/sample-publisher.service.spec.ts b/src/app/services/publisher/sample-publisher/sample-publisher.service.spec.ts deleted file mode 100644 index 3d2b923fa..000000000 --- a/src/app/services/publisher/sample-publisher/sample-publisher.service.spec.ts +++ /dev/null @@ -1 +0,0 @@ -describe('SamplePublisher', () => { }); diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 7ca2f07f7..e48386b13 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -47,6 +47,11 @@ "defaultInformationProvider": "Default Information Provider", "collectDeviceInfo": "Collect Device Info", "collectLocationInfo": "Collect Location Info", + "publisher": "Publisher", + "email": "Email", + "password": "Password", + "login": "Login", + "signUp": "Sign Up", "message": { "areYouSure": "This action cannot be undone.", "publishingProof": "Publishing proof {{hash}} to {{publisherName}}.", diff --git a/src/assets/image/n.svg b/src/assets/image/n.svg new file mode 100644 index 000000000..dd873fb97 --- /dev/null +++ b/src/assets/image/n.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/image/numbers.svg b/src/assets/image/numbers.svg new file mode 100644 index 000000000..5b1e6bade --- /dev/null +++ b/src/assets/image/numbers.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/shapes.svg b/src/assets/shapes.svg deleted file mode 100644 index d370b4dcc..000000000 --- a/src/assets/shapes.svg +++ /dev/null @@ -1 +0,0 @@ - From 9e939a8c8df7effca97316390fc58c6aa4803577 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Tue, 11 Aug 2020 23:12:46 +0800 Subject: [PATCH 02/31] WIP: build sign-up, login and logout pages. --- .gitignore | 1 + src/app/app.component.ts | 2 +- .../numbers-storage/login/login.page.html | 17 +++- .../numbers-storage/login/login.page.scss | 9 ++ .../numbers-storage/login/login.page.spec.ts | 11 ++- .../numbers-storage/login/login.page.ts | 40 ++++++-- .../numbers-storage/numbers-storage.page.html | 3 +- .../numbers-storage.page.spec.ts | 9 +- .../numbers-storage/numbers-storage.page.ts | 41 ++++++++ .../numbers-storage/sign-up/sign-up.page.html | 23 ++++- .../numbers-storage/sign-up/sign-up.page.scss | 12 +++ .../sign-up/sign-up.page.spec.ts | 11 ++- .../numbers-storage/sign-up/sign-up.page.ts | 41 ++++++-- .../numbers-storage-api.service.spec.ts | 18 ++++ .../numbers-storage-api.service.ts | 96 +++++++++++++++++++ .../numbers-storage-publisher.spec.ts | 1 + ...torage.ts => numbers-storage-publisher.ts} | 0 .../numbers-storage/numbers-storage.spec.ts | 1 - src/app/utils/encoding/encoding.spec.ts | 16 +++- src/app/utils/encoding/encoding.ts | 21 ++++ .../utils/preferences/preference-manager.ts | 4 +- src/app/utils/validators.ts | 10 ++ src/assets/i18n/en-us.json | 9 +- 23 files changed, 369 insertions(+), 27 deletions(-) create mode 100644 src/app/services/publisher/numbers-storage/numbers-storage-api.service.spec.ts create mode 100644 src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts create mode 100644 src/app/services/publisher/numbers-storage/numbers-storage-publisher.spec.ts rename src/app/services/publisher/numbers-storage/{numbers-storage.ts => numbers-storage-publisher.ts} (100%) delete mode 100644 src/app/services/publisher/numbers-storage/numbers-storage.spec.ts create mode 100644 src/app/utils/validators.ts diff --git a/.gitignore b/.gitignore index 14a08787d..c359dfa07 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ npm-debug.log* /plugins /www .gradle/ +secret.ts \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 555d472ac..c75e936d6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -10,7 +10,7 @@ import { InformationRepository } from './services/data/information/information-r import { SignatureRepository } from './services/data/signature/signature-repository.service'; import { LanguageService } from './services/language/language.service'; import { NotificationService } from './services/notification/notification.service'; -import { NumbersStoragePublisher } from './services/publisher/numbers-storage/numbers-storage'; +import { NumbersStoragePublisher } from './services/publisher/numbers-storage/numbers-storage-publisher'; import { PublishersAlert } from './services/publisher/publishers-alert/publishers-alert.service'; import { SerializationService } from './services/serialization/serialization.service'; diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.html b/src/app/pages/publishers/numbers-storage/login/login.page.html index 80d68ab1d..2b55d95e4 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.html +++ b/src/app/pages/publishers/numbers-storage/login/login.page.html @@ -2,7 +2,7 @@ - + @@ -18,15 +18,28 @@ {{ t('email') }} +
+

{{ t('required') }}

+

{{ t('message.isNotEmail') }}

+
{{ t('password') }} +
+

{{ t('required') }}

+

{{ t('tooShort') }}

+

{{ t('message.forbiddenAllNumeric') }}

+
{{ t('login') }} - {{ t('signUp') }} + + + {{ t('signUp') }} \ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.scss b/src/app/pages/publishers/numbers-storage/login/login.page.scss index 98582c5a0..6ebbd8d4e 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.scss +++ b/src/app/pages/publishers/numbers-storage/login/login.page.scss @@ -1,3 +1,12 @@ .numbers-logo { margin: 10vh 20vw; } + +.errors { + color: var(--ion-color-danger, red); + margin: 2px 0 8px; +} + +.errors p { + margin: 0; +} diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts b/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts index 4694aa6ac..3bda1ae75 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts +++ b/src/app/pages/publishers/numbers-storage/login/login.page.spec.ts @@ -1,6 +1,9 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; import { LoginPage } from './login.page'; describe('LoginPage', () => { @@ -10,7 +13,13 @@ describe('LoginPage', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LoginPage], - imports: [IonicModule.forRoot(), ReactiveFormsModule] + imports: [ + IonicModule.forRoot(), + ReactiveFormsModule, + RouterTestingModule, + TranslocoModule, + HttpClientTestingModule + ] }).compileComponents(); fixture = TestBed.createComponent(LoginPage); diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.ts b/src/app/pages/publishers/numbers-storage/login/login.page.ts index 03cf7706c..87cf27688 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.ts +++ b/src/app/pages/publishers/numbers-storage/login/login.page.ts @@ -1,6 +1,13 @@ import { Component } from '@angular/core'; -import { FormBuilder, Validators } from '@angular/forms'; +import { FormBuilder } from '@angular/forms'; +import { Router } from '@angular/router'; +import { LoadingController, ToastController } from '@ionic/angular'; +import { TranslocoService } from '@ngneat/transloco'; +import { UntilDestroy } from '@ngneat/until-destroy'; +import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; +import { emailValidators, passwordValidators } from 'src/app/utils/validators'; +@UntilDestroy({ checkProperties: true }) @Component({ selector: 'app-login', templateUrl: './login.page.html', @@ -8,16 +15,35 @@ import { FormBuilder, Validators } from '@angular/forms'; }) export class LoginPage { - loginForm = this.formBuilder.group({ - email: ['', [Validators.email, Validators.required]], - password: ['', Validators.required] + readonly loginForm = this.formBuilder.group({ + email: ['', emailValidators], + password: ['', passwordValidators] }); constructor( - private readonly formBuilder: FormBuilder + private readonly router: Router, + private readonly formBuilder: FormBuilder, + private readonly loadingController: LoadingController, + private readonly translocoService: TranslocoService, + private readonly toastController: ToastController, + private readonly numbersStorageApi: NumbersStorageApi ) { } - login() { - + // FIXME: cannot find a way to make this method readable and reactive + async login() { + const loading = await this.loadingController.create({ message: this.translocoService.translate('talkingToTheServer') }); + await loading.present(); + try { + await this.numbersStorageApi.login$( + this.loginForm.get('email')?.value, + this.loginForm.get('password')?.value + ).toPromise(); + await this.router.navigate(['/publishers', 'numbers-storage']); + } catch (e) { + console.log(e); + const toast = await this.toastController.create({ message: JSON.stringify(e.error), duration: 4000 }); + await toast.present(); + } + await loading.dismiss(); } } diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html index 94ae9134c..3cb8e4ba3 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html @@ -11,6 +11,5 @@ - go to login - go to sign up + {{ t('logout') }} \ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts index 798478fd7..f4d7a66aa 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.spec.ts @@ -1,6 +1,8 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; import { NumbersStoragePage } from './numbers-storage.page'; describe('NumbersStoragePage', () => { @@ -10,7 +12,12 @@ describe('NumbersStoragePage', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [NumbersStoragePage], - imports: [IonicModule.forRoot(), RouterTestingModule] + imports: [ + IonicModule.forRoot(), + RouterTestingModule, + TranslocoModule, + HttpClientTestingModule + ] }).compileComponents(); fixture = TestBed.createComponent(NumbersStoragePage); diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index bd2d07ac2..b1462fdf1 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -1,9 +1,50 @@ import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { LoadingController, ToastController } from '@ionic/angular'; +import { TranslocoService } from '@ngneat/transloco'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { tap } from 'rxjs/operators'; +import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; +@UntilDestroy({ checkProperties: true }) @Component({ selector: 'app-numbers-storage', templateUrl: './numbers-storage.page.html', styleUrls: ['./numbers-storage.page.scss'], }) export class NumbersStoragePage { + + constructor( + private readonly router: Router, + private readonly loadingController: LoadingController, + private readonly toastController: ToastController, + private readonly translocoService: TranslocoService, + private readonly numbersStorageApi: NumbersStorageApi + ) { } + + ionViewWillEnter() { + this.numbersStorageApi.isEnabled$().pipe( + tap(isEnabled => { + // FIXME: do not know why ['login'] does not work + if (!isEnabled) { this.router.navigate(['/publishers', 'numbers-storage', 'login']); } + }), + untilDestroyed(this) + ).subscribe(); + } + + // FIXME: cannot find a way to make this method readable and reactive + async logout() { + const loading = await this.loadingController.create({ message: this.translocoService.translate('talkingToTheServer') }); + await loading.present(); + try { + await this.numbersStorageApi.logout$().toPromise(); + // FIXME: do not know why ['login'] does not work + await this.router.navigate(['/publishers', 'numbers-storage', 'login']); + } catch (e) { + console.log(e); + const toast = await this.toastController.create({ message: JSON.stringify(e.error), duration: 4000 }); + await toast.present(); + } + await loading.dismiss(); + } } diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html index f4ab40b9b..72e0c2121 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html @@ -2,7 +2,7 @@ - + @@ -14,19 +14,38 @@
+ + + {{ t('userName') }} + +
+

{{ t('required') }}

+
+
{{ t('email') }} +
+

{{ t('required') }}

+

{{ t('message.isNotEmail') }}

+
{{ t('password') }} +
+

{{ t('required') }}

+

{{ t('tooShort') }}

+

{{ t('message.forbiddenAllNumeric') }}

+
{{ t('signUp') }}
- {{ t('signUp') }} \ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss index e69de29bb..6ebbd8d4e 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.scss @@ -0,0 +1,12 @@ +.numbers-logo { + margin: 10vh 20vw; +} + +.errors { + color: var(--ion-color-danger, red); + margin: 2px 0 8px; +} + +.errors p { + margin: 0; +} diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts index 7691ecb52..80ae24e28 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.spec.ts @@ -1,6 +1,9 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; import { SignUpPage } from './sign-up.page'; describe('SignUpPage', () => { @@ -10,7 +13,13 @@ describe('SignUpPage', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SignUpPage], - imports: [IonicModule.forRoot(), ReactiveFormsModule] + imports: [ + IonicModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + RouterTestingModule, + TranslocoModule + ] }).compileComponents(); fixture = TestBed.createComponent(SignUpPage); diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts index 0b15ee7a2..c1927048f 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts @@ -1,6 +1,13 @@ import { Component } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { LoadingController, ToastController } from '@ionic/angular'; +import { TranslocoService } from '@ngneat/transloco'; +import { UntilDestroy } from '@ngneat/until-destroy'; +import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; +import { emailValidators, passwordValidators } from 'src/app/utils/validators'; +@UntilDestroy({ checkProperties: true }) @Component({ selector: 'app-sign-up', templateUrl: './sign-up.page.html', @@ -8,16 +15,38 @@ import { FormBuilder, Validators } from '@angular/forms'; }) export class SignUpPage { - signUpForm = this.formBuilder.group({ - email: ['', [Validators.email, Validators.required]], - password: ['', Validators.required] + readonly signUpForm = this.formBuilder.group({ + userName: ['', Validators.required], + email: ['', emailValidators], + password: ['', passwordValidators] }); constructor( - private readonly formBuilder: FormBuilder + private readonly router: Router, + private readonly formBuilder: FormBuilder, + private readonly loadingController: LoadingController, + private readonly toastController: ToastController, + private readonly translocoService: TranslocoService, + private readonly numbersStorageApi: NumbersStorageApi ) { } - signUp() { - + // FIXME: cannot find a way to make this method readable and reactive + async signUp() { + const loading = await this.loadingController.create({ message: this.translocoService.translate('talkingToTheServer') }); + await loading.present(); + try { + await this.numbersStorageApi.createUser$( + this.signUpForm.get('userName')?.value, + this.signUpForm.get('email')?.value, + this.signUpForm.get('password')?.value + ).toPromise(); + // FIXME: do not know why ['..'] does not work + await this.router.navigate(['/publishers', 'numbers-storage']); + } catch (e) { + console.log(e); + const toast = await this.toastController.create({ message: JSON.stringify(e.error), duration: 4000 }); + await toast.present(); + } + await loading.dismiss(); } } diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.spec.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.spec.ts new file mode 100644 index 000000000..da686ea84 --- /dev/null +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.spec.ts @@ -0,0 +1,18 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { NumbersStorageApi } from './numbers-storage-api.service'; + +describe('NumbersStorageApi', () => { + let service: NumbersStorageApi; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule] + }); + service = TestBed.inject(NumbersStorageApi); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts new file mode 100644 index 000000000..25b7cec2e --- /dev/null +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -0,0 +1,96 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { zip } from 'rxjs'; +import { map, switchMap, switchMapTo } from 'rxjs/operators'; +import { base64ToBlob$ } from 'src/app/utils/encoding/encoding'; +import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; +import { baseUrl } from './secret'; + +const preference = PreferenceManager.NUMBERS_STORAGE_PUBLISHER_PREF; +const enum PrefKeys { + Enabled = 'enabled', + AuthToken = 'authToken' +} + +@Injectable({ + providedIn: 'root' +}) +export class NumbersStorageApi { + + constructor( + private readonly httpClient: HttpClient + ) { } + + isEnabled$() { + return preference.getBoolean$(PrefKeys.Enabled); + } + + createUser$( + userName: string, + email: string, + password: string + ) { + const formData = new FormData(); + formData.append('username', userName); + formData.append('email', email); + formData.append('password', password); + return this.httpClient.post(`${baseUrl}/auth/users/`, formData); + } + + login$( + email: string, + password: string + ) { + const formData = new FormData(); + formData.append('email', email); + formData.append('password', password); + return this.httpClient.post(`${baseUrl}/auth/token/login/`, formData).pipe( + switchMap(tokenCreate => { + return preference.setString$(PrefKeys.AuthToken, `token ${tokenCreate.auth_token}`); + }), + switchMapTo(preference.setBoolean$(PrefKeys.Enabled, true)) + ); + } + + logout$() { + return preference.setBoolean$(PrefKeys.Enabled, false).pipe( + switchMapTo(preference.getString$(PrefKeys.AuthToken)), + map(authToken => new HttpHeaders({ Authorization: authToken })), + switchMap(headers => this.httpClient.post(`${baseUrl}/auth/token/logout/`, new FormData(), { headers })) + ); + } + + createMedia$( + rawFileBase64: string, + information: string, + targetProvider: string, + caption: string, + signatures: string, + tag: string + ) { + return preference.getString$(PrefKeys.AuthToken).pipe( + switchMap(headers => zip(base64ToBlob$(rawFileBase64), headers)), + switchMap(([rawFile, authToken]) => { + const headers = new HttpHeaders({ Authorization: authToken }); + const formData = new FormData(); + formData.append('file', rawFile); + formData.append('meta', information); + formData.append('target_provider', targetProvider); + formData.append('caption', caption); + formData.append('signature', signatures); + formData.append('tag', tag); + return this.httpClient.post(`${baseUrl}/api/v1/media/`, formData, { headers }); + }) + ); + } +} + +interface User { + readonly username: string; + readonly email: string; + readonly id: number; +} + +interface TokenCreate { + readonly auth_token: string; +} diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.spec.ts b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.spec.ts new file mode 100644 index 000000000..1ca772a66 --- /dev/null +++ b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.spec.ts @@ -0,0 +1 @@ +describe('NumbersStoragePublisher', () => { }); diff --git a/src/app/services/publisher/numbers-storage/numbers-storage.ts b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts similarity index 100% rename from src/app/services/publisher/numbers-storage/numbers-storage.ts rename to src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts diff --git a/src/app/services/publisher/numbers-storage/numbers-storage.spec.ts b/src/app/services/publisher/numbers-storage/numbers-storage.spec.ts deleted file mode 100644 index 9e2858e8b..000000000 --- a/src/app/services/publisher/numbers-storage/numbers-storage.spec.ts +++ /dev/null @@ -1 +0,0 @@ -describe('NumbersStorage', () => { }); diff --git a/src/app/utils/encoding/encoding.spec.ts b/src/app/utils/encoding/encoding.spec.ts index 797370efe..abb1da2d8 100644 --- a/src/app/utils/encoding/encoding.spec.ts +++ b/src/app/utils/encoding/encoding.spec.ts @@ -1,4 +1,8 @@ -import { arrayBufferToBase64, arrayBufferToHex, arrayBufferToString, base64ToArrayBuffer, hexToArrayBuffer, stringToArrayBuffer } from './encoding'; +import { concatMap } from 'rxjs/operators'; +import { + arrayBufferToBase64, arrayBufferToHex, arrayBufferToString, base64ToArrayBuffer, base64ToBlob$, + blobToBase64$, hexToArrayBuffer, stringToArrayBuffer +} from './encoding'; describe('encoding', () => { @@ -16,4 +20,14 @@ describe('encoding', () => { const expected = 'hello'; expect(arrayBufferToString(stringToArrayBuffer(expected))).toEqual(expected); }); + + it('test base64ToBlobConversion', (done: DoneFn) => { + const expected = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAA9aVRYdENyZWF0aW9uIFRpbWUAAAAAADIwMjDlubTlhavmnIgxMeaXpSAo6YCx5LqMKSAxMeaZgjIw5YiGMTbnp5JpXef5AAAAP0lEQVQImWOUk5P7z4AFMGETZGBgYGA+e3xSg7uFFAPD/dsM19/8QUhcvvOz4auWNUNGlATDnTWXGR7/g0gAAFsdErMML91eAAAAAElFTkSuQmCC'; + base64ToBlob$(expected).pipe( + concatMap(blob => blobToBase64$(blob)) + ).subscribe(base64 => { + expect(base64).toEqual(expected); + done(); + }); + }); }); diff --git a/src/app/utils/encoding/encoding.ts b/src/app/utils/encoding/encoding.ts index e01982072..5d8cd64ae 100644 --- a/src/app/utils/encoding/encoding.ts +++ b/src/app/utils/encoding/encoding.ts @@ -1,3 +1,6 @@ +import { defer, Subject } from 'rxjs'; +import { first, switchMap } from 'rxjs/operators'; + const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); @@ -32,3 +35,21 @@ export function stringToArrayBuffer(str: string) { export function arrayBufferToString(arrayBuffer: ArrayBuffer) { return textDecoder.decode(arrayBuffer); } + +export function base64ToBlob$(base64: string) { + return defer(() => fetch(base64)).pipe( + first(), + switchMap(res => res.blob()) + ); +} + +export function blobToBase64$(blob: Blob) { + const fileReader = new FileReader(); + const subject$ = new Subject(); + fileReader.onloadend = () => { + subject$.next(fileReader.result as string); + subject$.complete(); + }; + fileReader.readAsDataURL(blob); + return subject$.asObservable(); +} diff --git a/src/app/utils/preferences/preference-manager.ts b/src/app/utils/preferences/preference-manager.ts index 1c46ff93b..bffc7b4de 100644 --- a/src/app/utils/preferences/preference-manager.ts +++ b/src/app/utils/preferences/preference-manager.ts @@ -3,7 +3,8 @@ import { Preferences } from './preferences'; const enum RepositoryName { Language = 'language', DefaultInformationProvider = 'defaultInformationProvider', - DefaultSignatureProvider = 'defaultSignatureProvider' + DefaultSignatureProvider = 'defaultSignatureProvider', + NumbersStoragePublisher = 'numbersStoragePublisher' } export class PreferenceManager { @@ -11,4 +12,5 @@ export class PreferenceManager { static readonly LANGUAGE_PREF = new Preferences(RepositoryName.Language); static readonly DEFAULT_INFORMATION_PROVIDER_PREF = new Preferences(RepositoryName.DefaultInformationProvider); static readonly DEFAULT_SIGNATURE_PROVIDER_PREF = new Preferences(RepositoryName.DefaultSignatureProvider); + static readonly NUMBERS_STORAGE_PUBLISHER_PREF = new Preferences(RepositoryName.NumbersStoragePublisher); } diff --git a/src/app/utils/validators.ts b/src/app/utils/validators.ts new file mode 100644 index 000000000..e3f05558b --- /dev/null +++ b/src/app/utils/validators.ts @@ -0,0 +1,10 @@ +import { AbstractControl, ValidationErrors, Validators } from '@angular/forms'; + +export const emailValidators = [Validators.required, Validators.email]; +export const passwordValidators = [Validators.required, Validators.minLength(8), forbiddenAllNumericValidator]; + +function forbiddenAllNumericValidator(control: AbstractControl): ValidationErrors | null { + const isNumber = /^\d+$/.test(control.value); + // tslint:disable-next-line: no-null-keyword + return isNumber ? { allNumeric: { value: control.value } } : null; +} diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index e48386b13..22f3573fe 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -48,13 +48,20 @@ "collectDeviceInfo": "Collect Device Info", "collectLocationInfo": "Collect Location Info", "publisher": "Publisher", + "userName": "User Name", "email": "Email", "password": "Password", "login": "Login", + "logout": "Logout", "signUp": "Sign Up", + "required": "Required", + "tooShort": "Too Short", + "talkingToTheServer": "Talking to the Server", "message": { "areYouSure": "This action cannot be undone.", "publishingProof": "Publishing proof {{hash}} to {{publisherName}}.", - "proofPublished": "Proof {{hash}} has been published to {{publisherName}}." + "proofPublished": "Proof {{hash}} has been published to {{publisherName}}.", + "forbiddenAllNumeric": "Cannot contain only numbers.", + "isNotEmail": "Does not follow email format." } } \ No newline at end of file From 404a5d77ab6839ac1b8adaf875453869b3e78cec Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 12 Aug 2020 11:17:53 +0800 Subject: [PATCH 03/31] Add empty secret to make CI workable. --- src/app/services/publisher/numbers-storage/secret.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/app/services/publisher/numbers-storage/secret.ts diff --git a/src/app/services/publisher/numbers-storage/secret.ts b/src/app/services/publisher/numbers-storage/secret.ts new file mode 100644 index 000000000..f6e2d6f84 --- /dev/null +++ b/src/app/services/publisher/numbers-storage/secret.ts @@ -0,0 +1 @@ +export const baseUrl = ''; From da1f5ea6c78054e44fc19a327926cca20c69e5cc Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 12 Aug 2020 11:38:36 +0800 Subject: [PATCH 04/31] Add description to internal branch. --- .gitignore | 3 +-- README.md | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c359dfa07..c577fc62c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,4 @@ npm-debug.log* /platforms /plugins /www -.gradle/ -secret.ts \ No newline at end of file +.gradle/ \ No newline at end of file diff --git a/README.md b/README.md index ed98e7e17..1d072be04 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,19 @@ | master | [![build](https://github.com/numbersprotocol/capture-lite/workflows/build/badge.svg)](https://github.com/numbersprotocol/capture-lite/actions?query=workflow%3Abuild) | [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/45ae18aaa6a7474497e0efd818452a46)](https://www.codacy.com/gh/numbersprotocol/capture-lite?utm_source=github.com&utm_medium=referral&utm_content=numbersprotocol/capture-lite&utm_campaign=Badge_Coverage) | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/45ae18aaa6a7474497e0efd818452a46)](https://www.codacy.com/gh/numbersprotocol/capture-lite?utm_source=github.com&utm_medium=referral&utm_content=numbersprotocol/capture-lite&utm_campaign=Badge_Grade) | | develop | [![build](https://github.com/numbersprotocol/capture-lite/workflows/build/badge.svg?branch=develop)](https://github.com/numbersprotocol/capture-lite/actions?query=workflow%3Abuild) | [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/45ae18aaa6a7474497e0efd818452a46?branch=develop)](https://www.codacy.com/gh/numbersprotocol/capture-lite?utm_source=github.com&utm_medium=referral&utm_content=numbersprotocol/capture-lite&utm_campaign=Badge_Coverage) | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/45ae18aaa6a7474497e0efd818452a46?branch=develop)](https://www.codacy.com/gh/numbersprotocol/capture-lite?utm_source=github.com&utm_medium=referral&utm_content=numbersprotocol/capture-lite&utm_campaign=Badge_Grade) +## Internal Branch + +__This is an internal branch which cannot be built without further configurations.__ + +### Ignore Secrets in Git + +1. Create an empty `secret.ts` file with empty variables (e.g. empty string). +1. Push the empty secret file to remote repo so the CI can still build the project. +1. Make git ignore the modification of the secret file locally with the command `git update-index --skip-worktree `. +1. Now, you can modify the secret file without the fear of pushing it to the public repo. + +> You don't even need to add the secret file to `.gitignore`. + ## Highlight Features * Generate digital proofs on media assets created. From 63b52e6f6fd8ebe3b9d5896aacb397929f951429 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 12 Aug 2020 15:50:44 +0800 Subject: [PATCH 05/31] WIP: Refactor login method to be more reactive. --- .../numbers-storage/login/login.page.html | 11 ----- .../numbers-storage/login/login.page.ts | 42 ++++++++++--------- .../numbers-storage-api.service.ts | 4 +- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.html b/src/app/pages/publishers/numbers-storage/login/login.page.html index 2b55d95e4..56d7ff340 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.html +++ b/src/app/pages/publishers/numbers-storage/login/login.page.html @@ -18,22 +18,11 @@ {{ t('email') }} -
-

{{ t('required') }}

-

{{ t('message.isNotEmail') }}

-
{{ t('password') }} -
-

{{ t('required') }}

-

{{ t('tooShort') }}

-

{{ t('message.forbiddenAllNumeric') }}

-
{{ t('login') }} diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.ts b/src/app/pages/publishers/numbers-storage/login/login.page.ts index 87cf27688..6f27f6c63 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.ts +++ b/src/app/pages/publishers/numbers-storage/login/login.page.ts @@ -1,9 +1,12 @@ import { Component } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; -import { LoadingController, ToastController } from '@ionic/angular'; +import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; -import { UntilDestroy } from '@ngneat/until-destroy'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { defer } from 'rxjs'; +import { catchError, switchMapTo } from 'rxjs/operators'; +import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; import { emailValidators, passwordValidators } from 'src/app/utils/validators'; @@ -23,27 +26,28 @@ export class LoginPage { constructor( private readonly router: Router, private readonly formBuilder: FormBuilder, - private readonly loadingController: LoadingController, private readonly translocoService: TranslocoService, + private readonly blockingActionService: BlockingActionService, private readonly toastController: ToastController, private readonly numbersStorageApi: NumbersStorageApi ) { } - // FIXME: cannot find a way to make this method readable and reactive - async login() { - const loading = await this.loadingController.create({ message: this.translocoService.translate('talkingToTheServer') }); - await loading.present(); - try { - await this.numbersStorageApi.login$( - this.loginForm.get('email')?.value, - this.loginForm.get('password')?.value - ).toPromise(); - await this.router.navigate(['/publishers', 'numbers-storage']); - } catch (e) { - console.log(e); - const toast = await this.toastController.create({ message: JSON.stringify(e.error), duration: 4000 }); - await toast.present(); - } - await loading.dismiss(); + login() { + const action$ = this.numbersStorageApi.login$( + this.loginForm.get('email')?.value, + this.loginForm.get('password')?.value + ).pipe( + catchError(err => this.toastController + .create({ message: JSON.stringify(err.error), duration: 4000 }) + .then(toast => toast.present()) + ), + switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage']))) + ); + this.blockingActionService.run$( + action$, + { message: this.translocoService.translate('talkingToTheServer') } + ).pipe( + untilDestroyed(this) + ).subscribe(); } } diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts index 25b7cec2e..328360f93 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -45,9 +45,7 @@ export class NumbersStorageApi { formData.append('email', email); formData.append('password', password); return this.httpClient.post(`${baseUrl}/auth/token/login/`, formData).pipe( - switchMap(tokenCreate => { - return preference.setString$(PrefKeys.AuthToken, `token ${tokenCreate.auth_token}`); - }), + switchMap(tokenCreate => preference.setString$(PrefKeys.AuthToken, `token ${tokenCreate.auth_token}`)), switchMapTo(preference.setBoolean$(PrefKeys.Enabled, true)) ); } From 3b1c20c969eec8e05bacd641d1204f1d98702856 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 12 Aug 2020 16:21:45 +0800 Subject: [PATCH 06/31] Make http requests more reactive. --- .../numbers-storage/login/login.page.ts | 5 ++- .../numbers-storage/numbers-storage.page.ts | 35 ++++++++------- .../numbers-storage/sign-up/sign-up.page.ts | 44 ++++++++++--------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.ts b/src/app/pages/publishers/numbers-storage/login/login.page.ts index 6f27f6c63..68c747e8a 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.ts +++ b/src/app/pages/publishers/numbers-storage/login/login.page.ts @@ -37,11 +37,12 @@ export class LoginPage { this.loginForm.get('email')?.value, this.loginForm.get('password')?.value ).pipe( + // FIXME: do not know why ['..'] does not work + switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage']))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) - ), - switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage']))) + ) ); this.blockingActionService.run$( action$, diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index b1462fdf1..ff58a8646 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -1,9 +1,11 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; -import { LoadingController, ToastController } from '@ionic/angular'; +import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { tap } from 'rxjs/operators'; +import { defer } from 'rxjs'; +import { catchError, switchMapTo, tap } from 'rxjs/operators'; +import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; @UntilDestroy({ checkProperties: true }) @@ -16,7 +18,7 @@ export class NumbersStoragePage { constructor( private readonly router: Router, - private readonly loadingController: LoadingController, + private readonly blockingActionService: BlockingActionService, private readonly toastController: ToastController, private readonly translocoService: TranslocoService, private readonly numbersStorageApi: NumbersStorageApi @@ -32,19 +34,20 @@ export class NumbersStoragePage { ).subscribe(); } - // FIXME: cannot find a way to make this method readable and reactive - async logout() { - const loading = await this.loadingController.create({ message: this.translocoService.translate('talkingToTheServer') }); - await loading.present(); - try { - await this.numbersStorageApi.logout$().toPromise(); + logout() { + const action$ = this.numbersStorageApi.logout$().pipe( // FIXME: do not know why ['login'] does not work - await this.router.navigate(['/publishers', 'numbers-storage', 'login']); - } catch (e) { - console.log(e); - const toast = await this.toastController.create({ message: JSON.stringify(e.error), duration: 4000 }); - await toast.present(); - } - await loading.dismiss(); + switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage', 'login']))), + catchError(err => this.toastController + .create({ message: JSON.stringify(err.error), duration: 4000 }) + .then(toast => toast.present()) + ) + ); + this.blockingActionService.run$( + action$, + { message: this.translocoService.translate('talkingToTheServer') } + ).pipe( + untilDestroyed(this) + ).subscribe(); } } diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts index c1927048f..38b6666dd 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts @@ -1,9 +1,12 @@ import { Component } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { LoadingController, ToastController } from '@ionic/angular'; +import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; -import { UntilDestroy } from '@ngneat/until-destroy'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { defer } from 'rxjs'; +import { catchError, switchMapTo } from 'rxjs/operators'; +import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; import { emailValidators, passwordValidators } from 'src/app/utils/validators'; @@ -24,29 +27,30 @@ export class SignUpPage { constructor( private readonly router: Router, private readonly formBuilder: FormBuilder, - private readonly loadingController: LoadingController, + private readonly blockingActionService: BlockingActionService, private readonly toastController: ToastController, private readonly translocoService: TranslocoService, private readonly numbersStorageApi: NumbersStorageApi ) { } - // FIXME: cannot find a way to make this method readable and reactive - async signUp() { - const loading = await this.loadingController.create({ message: this.translocoService.translate('talkingToTheServer') }); - await loading.present(); - try { - await this.numbersStorageApi.createUser$( - this.signUpForm.get('userName')?.value, - this.signUpForm.get('email')?.value, - this.signUpForm.get('password')?.value - ).toPromise(); + signUp() { + const action$ = this.numbersStorageApi.createUser$( + this.signUpForm.get('userName')?.value, + this.signUpForm.get('email')?.value, + this.signUpForm.get('password')?.value + ).pipe( // FIXME: do not know why ['..'] does not work - await this.router.navigate(['/publishers', 'numbers-storage']); - } catch (e) { - console.log(e); - const toast = await this.toastController.create({ message: JSON.stringify(e.error), duration: 4000 }); - await toast.present(); - } - await loading.dismiss(); + switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage']))), + catchError(err => this.toastController + .create({ message: JSON.stringify(err.error), duration: 4000 }) + .then(toast => toast.present()) + ) + ); + this.blockingActionService.run$( + action$, + { message: this.translocoService.translate('talkingToTheServer') } + ).pipe( + untilDestroyed(this) + ).subscribe(); } } From 98cceddbf9c51a9c0ef74e2413d67368447bfafd Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Thu, 13 Aug 2020 17:42:25 +0800 Subject: [PATCH 07/31] WIP: Use relative path in nested page module. --- .../numbers-storage/login/login.page.html | 6 ++---- .../numbers-storage/login/login.page.ts | 6 +++--- .../numbers-storage/numbers-storage.page.html | 5 ++++- .../numbers-storage/numbers-storage.page.ts | 20 +++++++------------ .../numbers-storage/sign-up/sign-up.page.html | 3 +-- .../numbers-storage/sign-up/sign-up.page.ts | 6 +++--- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.html b/src/app/pages/publishers/numbers-storage/login/login.page.html index 56d7ff340..719c9fcfa 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.html +++ b/src/app/pages/publishers/numbers-storage/login/login.page.html @@ -1,8 +1,7 @@ - - + @@ -28,7 +27,6 @@ {{ t('login') }} - - + {{ t('signUp') }} \ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.ts b/src/app/pages/publishers/numbers-storage/login/login.page.ts index 68c747e8a..ff3c03fef 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.ts +++ b/src/app/pages/publishers/numbers-storage/login/login.page.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { FormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -25,6 +25,7 @@ export class LoginPage { constructor( private readonly router: Router, + private readonly route: ActivatedRoute, private readonly formBuilder: FormBuilder, private readonly translocoService: TranslocoService, private readonly blockingActionService: BlockingActionService, @@ -37,8 +38,7 @@ export class LoginPage { this.loginForm.get('email')?.value, this.loginForm.get('password')?.value ).pipe( - // FIXME: do not know why ['..'] does not work - switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage']))), + switchMapTo(defer(() => this.router.navigate(['..'], { relativeTo: this.route }))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html index 3cb8e4ba3..f94f71895 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html @@ -11,5 +11,8 @@ - {{ t('logout') }} + {{ t('login') }} + + {{ t('logout') }} + \ No newline at end of file diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index ff58a8646..1fb63473f 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -16,28 +16,22 @@ import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/nu }) export class NumbersStoragePage { + readonly isEnabled$ = this.numbersStorageApi.isEnabled$().pipe( + tap(v => console.log(v)) + ); + constructor( private readonly router: Router, + private readonly route: ActivatedRoute, private readonly blockingActionService: BlockingActionService, private readonly toastController: ToastController, private readonly translocoService: TranslocoService, private readonly numbersStorageApi: NumbersStorageApi ) { } - ionViewWillEnter() { - this.numbersStorageApi.isEnabled$().pipe( - tap(isEnabled => { - // FIXME: do not know why ['login'] does not work - if (!isEnabled) { this.router.navigate(['/publishers', 'numbers-storage', 'login']); } - }), - untilDestroyed(this) - ).subscribe(); - } - logout() { const action$ = this.numbersStorageApi.logout$().pipe( - // FIXME: do not know why ['login'] does not work - switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage', 'login']))), + switchMapTo(defer(() => this.router.navigate(['login'], { relativeTo: this.route }))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html index 72e0c2121..0d0266507 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.html @@ -1,8 +1,7 @@ - - + diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts index 38b6666dd..6aac3e0ea 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -26,6 +26,7 @@ export class SignUpPage { constructor( private readonly router: Router, + private readonly route: ActivatedRoute, private readonly formBuilder: FormBuilder, private readonly blockingActionService: BlockingActionService, private readonly toastController: ToastController, @@ -39,8 +40,7 @@ export class SignUpPage { this.signUpForm.get('email')?.value, this.signUpForm.get('password')?.value ).pipe( - // FIXME: do not know why ['..'] does not work - switchMapTo(defer(() => this.router.navigate(['/publishers', 'numbers-storage']))), + switchMapTo(defer(() => this.router.navigate(['..'], { relativeTo: this.route }))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) From db32ade7c7474ea20cfb7fe5ff15fe2f0d03c536 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Sat, 15 Aug 2020 11:29:26 +0800 Subject: [PATCH 08/31] Implement numbers-storage publisher login and signup pages. --- .../numbers-storage/numbers-storage.page.html | 3 +- .../numbers-storage/numbers-storage.page.ts | 6 +-- .../numbers-storage-publisher.ts | 15 ++++++ src/app/services/publisher/publisher.ts | 2 + .../publishers-alert.service.ts | 48 ++++++++++++------- 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html index f94f71895..2173d6031 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html @@ -11,7 +11,8 @@ - {{ t('login') }} + + {{ t('login') }} {{ t('logout') }} diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index 1fb63473f..0001e73a8 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -4,7 +4,7 @@ import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { defer } from 'rxjs'; -import { catchError, switchMapTo, tap } from 'rxjs/operators'; +import { catchError, switchMapTo } from 'rxjs/operators'; import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; @@ -16,9 +16,7 @@ import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/nu }) export class NumbersStoragePage { - readonly isEnabled$ = this.numbersStorageApi.isEnabled$().pipe( - tap(v => console.log(v)) - ); + readonly isEnabled$ = this.numbersStorageApi.isEnabled$(); constructor( private readonly router: Router, diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts index 259a352fe..476c3121f 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts @@ -1,12 +1,27 @@ +import { TranslocoService } from '@ngneat/transloco'; import { Observable, of } from 'rxjs'; import { delay, tap } from 'rxjs/operators'; import { Proof } from '../../data/proof/proof'; +import { NotificationService } from '../../notification/notification.service'; import { Publisher } from '../publisher'; +import { NumbersStorageApi } from './numbers-storage-api.service'; export class NumbersStoragePublisher extends Publisher { readonly name = 'Numbers Storage'; + constructor( + translocoService: TranslocoService, + notificationService: NotificationService, + private readonly numbersStorageApi: NumbersStorageApi + ) { + super(translocoService, notificationService); + } + + isEnabled$() { + return this.numbersStorageApi.isEnabled$(); + } + run$(proof: Proof): Observable { return of(void 0).pipe( tap(_ => console.log(`Start publishing ${proof.hash} from ${this.name}.`)), diff --git a/src/app/services/publisher/publisher.ts b/src/app/services/publisher/publisher.ts index 72c6ab59c..39ed8f52d 100644 --- a/src/app/services/publisher/publisher.ts +++ b/src/app/services/publisher/publisher.ts @@ -32,5 +32,7 @@ export abstract class Publisher { ); } + abstract isEnabled$(): Observable; + protected abstract run$(proof: Proof): Observable; } diff --git a/src/app/services/publisher/publishers-alert/publishers-alert.service.ts b/src/app/services/publisher/publishers-alert/publishers-alert.service.ts index 80567cf9b..02774741b 100644 --- a/src/app/services/publisher/publishers-alert/publishers-alert.service.ts +++ b/src/app/services/publisher/publishers-alert/publishers-alert.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { AlertController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; -import { defer } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { forkJoin, of, zip } from 'rxjs'; +import { first, map, switchMap } from 'rxjs/operators'; import { Proof } from '../../data/proof/proof'; import { Publisher } from '../publisher'; @@ -23,27 +23,39 @@ export class PublishersAlert { } present$(proof: Proof) { - return defer(() => this.alertController.create({ - header: this.translocoService.translate('selectAPublisher'), - inputs: this.publishers.map((publisher, index) => ({ - name: publisher.name, - type: 'radio', - label: publisher.name, - value: publisher.name, - checked: index === 0 + return this.getEnabledPublishers$().pipe( + switchMap(publishers => this.alertController.create({ + header: this.translocoService.translate('selectAPublisher'), + inputs: publishers.map((publisher, index) => ({ + name: publisher.name, + type: 'radio', + label: publisher.name, + value: publisher.name, + checked: index === 0 + })), + buttons: [{ + text: this.translocoService.translate('cancel'), + role: 'cancel' + }, { + text: this.translocoService.translate('ok'), + handler: (name) => this.getPublisherByName(name)?.publish(proof) + }] })), - buttons: [{ - text: this.translocoService.translate('cancel'), - role: 'cancel' - }, { - text: this.translocoService.translate('ok'), - handler: (name) => this.getPublisherByName(name)?.publish(proof) - }] - })).pipe( switchMap(alertElement => alertElement.present()) ); } + // XXX: Use toArray to replace forkJoin in many places!! + // https://stackoverflow.com/questions/52156063/how-to-filter-out-observable-values-based-on-observable-property-of-each-value + private getEnabledPublishers$() { + return forkJoin(this.publishers.map(publisher => zip( + of(publisher), + publisher.isEnabled$().pipe(first()) + ))).pipe( + map(publishersWithisEnabled => publishersWithisEnabled.filter(v => v[1]).map(v => v[0])) + ); + } + private getPublisherByName(name: string) { return this.publishers.find(publisher => publisher.name === name); } From 5c4f47ce39e2f13f229c0d45573d072252caa4a9 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Sun, 16 Aug 2020 00:00:34 +0800 Subject: [PATCH 09/31] Add NumbersStorageApi dependency when create the publisher. --- src/app/app.component.spec.ts | 3 ++- src/app/app.component.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 75237691a..61dc9599d 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,3 +1,4 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { async, TestBed } from '@angular/core/testing'; import { Platform } from '@ionic/angular'; @@ -16,7 +17,7 @@ describe('AppComponent', () => { TestBed.configureTestingModule({ declarations: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], - imports: [getTranslocoModule()], + imports: [getTranslocoModule(), HttpClientTestingModule], providers: [ { provide: Platform, useValue: platformSpy } ], diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c75e936d6..98e0af330 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -10,6 +10,7 @@ import { InformationRepository } from './services/data/information/information-r import { SignatureRepository } from './services/data/signature/signature-repository.service'; import { LanguageService } from './services/language/language.service'; import { NotificationService } from './services/notification/notification.service'; +import { NumbersStorageApi } from './services/publisher/numbers-storage/numbers-storage-api.service'; import { NumbersStoragePublisher } from './services/publisher/numbers-storage/numbers-storage-publisher'; import { PublishersAlert } from './services/publisher/publishers-alert/publishers-alert.service'; import { SerializationService } from './services/serialization/serialization.service'; @@ -32,6 +33,7 @@ export class AppComponent { private readonly signatureRepository: SignatureRepository, private readonly translocoService: TranslocoService, private readonly notificationService: NotificationService, + private readonly numbersStorageApi: NumbersStorageApi, langaugeService: LanguageService ) { this.initializeApp(); @@ -58,7 +60,7 @@ export class AppComponent { initializePublisher() { this.publishersAlert.addPublisher( - new NumbersStoragePublisher(this.translocoService, this.notificationService) + new NumbersStoragePublisher(this.translocoService, this.notificationService, this.numbersStorageApi) ); } } From 4935f1aa0995885ff3db00fceb3d9cf4b256fa8e Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Sun, 16 Aug 2020 16:02:31 +0800 Subject: [PATCH 10/31] Show user information. --- .../numbers-storage/numbers-storage.page.html | 12 +++++++ .../numbers-storage/numbers-storage.page.ts | 2 ++ .../numbers-storage-api.service.ts | 31 ++++++++++++++++--- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html index 2173d6031..8e9217643 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.html +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.html @@ -11,6 +11,18 @@ + + + + {{ t('userName') }} + {{ userName$ | async }} + + + + {{ t('email') }} + {{ email$ | async }} + + {{ t('login') }} diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index 0001e73a8..fc538442f 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -17,6 +17,8 @@ import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/nu export class NumbersStoragePage { readonly isEnabled$ = this.numbersStorageApi.isEnabled$(); + readonly userName$ = this.numbersStorageApi.userName$; + readonly email$ = this.numbersStorageApi.email$; constructor( private readonly router: Router, diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts index 328360f93..01c53e69d 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { zip } from 'rxjs'; -import { map, switchMap, switchMapTo } from 'rxjs/operators'; +import { concatMap, concatMapTo, map, pluck, switchMap, switchMapTo } from 'rxjs/operators'; import { base64ToBlob$ } from 'src/app/utils/encoding/encoding'; import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; import { baseUrl } from './secret'; @@ -9,7 +9,9 @@ import { baseUrl } from './secret'; const preference = PreferenceManager.NUMBERS_STORAGE_PUBLISHER_PREF; const enum PrefKeys { Enabled = 'enabled', - AuthToken = 'authToken' + AuthToken = 'authToken', + UserName = 'userName', + Email = 'email' } @Injectable({ @@ -25,6 +27,14 @@ export class NumbersStorageApi { return preference.getBoolean$(PrefKeys.Enabled); } + get userName$() { + return preference.getString$(PrefKeys.UserName); + } + + get email$() { + return preference.getString$(PrefKeys.Email); + } + createUser$( userName: string, email: string, @@ -45,8 +55,21 @@ export class NumbersStorageApi { formData.append('email', email); formData.append('password', password); return this.httpClient.post(`${baseUrl}/auth/token/login/`, formData).pipe( - switchMap(tokenCreate => preference.setString$(PrefKeys.AuthToken, `token ${tokenCreate.auth_token}`)), - switchMapTo(preference.setBoolean$(PrefKeys.Enabled, true)) + pluck('auth_token'), + concatMap(authToken => preference.setString$(PrefKeys.AuthToken, `token ${authToken}`)), + concatMapTo(this.getUserInformation$()), + concatMap(user => zip( + preference.setString$(PrefKeys.UserName, user.username), + preference.setString$(PrefKeys.Email, user.email) + )), + concatMapTo(preference.setBoolean$(PrefKeys.Enabled, true)) + ); + } + + getUserInformation$() { + return preference.getString$(PrefKeys.AuthToken).pipe( + map(authToken => new HttpHeaders({ Authorization: authToken })), + switchMap(headers => this.httpClient.get(`${baseUrl}/auth/users/me/`, { headers })) ); } From 617eb0f681f1e4c6a9d84a30536d6e457aaa34bd Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Sun, 16 Aug 2020 16:22:53 +0800 Subject: [PATCH 11/31] Use concatMap instead of switchMap for predictable process sequence. --- .../publishers/numbers-storage/login/login.page.ts | 4 ++-- .../numbers-storage/numbers-storage.page.ts | 4 ++-- .../numbers-storage/sign-up/sign-up.page.ts | 4 ++-- .../numbers-storage/numbers-storage-api.service.ts | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/pages/publishers/numbers-storage/login/login.page.ts b/src/app/pages/publishers/numbers-storage/login/login.page.ts index ff3c03fef..f43dc4e89 100644 --- a/src/app/pages/publishers/numbers-storage/login/login.page.ts +++ b/src/app/pages/publishers/numbers-storage/login/login.page.ts @@ -5,7 +5,7 @@ import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { defer } from 'rxjs'; -import { catchError, switchMapTo } from 'rxjs/operators'; +import { catchError, concatMapTo } from 'rxjs/operators'; import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; import { emailValidators, passwordValidators } from 'src/app/utils/validators'; @@ -38,7 +38,7 @@ export class LoginPage { this.loginForm.get('email')?.value, this.loginForm.get('password')?.value ).pipe( - switchMapTo(defer(() => this.router.navigate(['..'], { relativeTo: this.route }))), + concatMapTo(defer(() => this.router.navigate(['..'], { relativeTo: this.route }))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index fc538442f..2600d33e5 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -4,7 +4,7 @@ import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { defer } from 'rxjs'; -import { catchError, switchMapTo } from 'rxjs/operators'; +import { catchError, concatMapTo } from 'rxjs/operators'; import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; @@ -31,7 +31,7 @@ export class NumbersStoragePage { logout() { const action$ = this.numbersStorageApi.logout$().pipe( - switchMapTo(defer(() => this.router.navigate(['login'], { relativeTo: this.route }))), + concatMapTo(defer(() => this.router.navigate(['login'], { relativeTo: this.route }))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) diff --git a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts index 6aac3e0ea..044de04b8 100644 --- a/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts +++ b/src/app/pages/publishers/numbers-storage/sign-up/sign-up.page.ts @@ -5,7 +5,7 @@ import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { defer } from 'rxjs'; -import { catchError, switchMapTo } from 'rxjs/operators'; +import { catchError, concatMapTo } from 'rxjs/operators'; import { BlockingActionService } from 'src/app/services/blocking-action/blocking-action.service'; import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; import { emailValidators, passwordValidators } from 'src/app/utils/validators'; @@ -40,7 +40,7 @@ export class SignUpPage { this.signUpForm.get('email')?.value, this.signUpForm.get('password')?.value ).pipe( - switchMapTo(defer(() => this.router.navigate(['..'], { relativeTo: this.route }))), + concatMapTo(defer(() => this.router.navigate(['..'], { relativeTo: this.route }))), catchError(err => this.toastController .create({ message: JSON.stringify(err.error), duration: 4000 }) .then(toast => toast.present()) diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts index 01c53e69d..caf57a424 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { zip } from 'rxjs'; -import { concatMap, concatMapTo, map, pluck, switchMap, switchMapTo } from 'rxjs/operators'; +import { concatMap, concatMapTo, map, pluck } from 'rxjs/operators'; import { base64ToBlob$ } from 'src/app/utils/encoding/encoding'; import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; import { baseUrl } from './secret'; @@ -69,15 +69,15 @@ export class NumbersStorageApi { getUserInformation$() { return preference.getString$(PrefKeys.AuthToken).pipe( map(authToken => new HttpHeaders({ Authorization: authToken })), - switchMap(headers => this.httpClient.get(`${baseUrl}/auth/users/me/`, { headers })) + concatMap(headers => this.httpClient.get(`${baseUrl}/auth/users/me/`, { headers })) ); } logout$() { return preference.setBoolean$(PrefKeys.Enabled, false).pipe( - switchMapTo(preference.getString$(PrefKeys.AuthToken)), + concatMapTo(preference.getString$(PrefKeys.AuthToken)), map(authToken => new HttpHeaders({ Authorization: authToken })), - switchMap(headers => this.httpClient.post(`${baseUrl}/auth/token/logout/`, new FormData(), { headers })) + concatMap(headers => this.httpClient.post(`${baseUrl}/auth/token/logout/`, new FormData(), { headers })) ); } @@ -90,8 +90,8 @@ export class NumbersStorageApi { tag: string ) { return preference.getString$(PrefKeys.AuthToken).pipe( - switchMap(headers => zip(base64ToBlob$(rawFileBase64), headers)), - switchMap(([rawFile, authToken]) => { + concatMap(headers => zip(base64ToBlob$(rawFileBase64), headers)), + concatMap(([rawFile, authToken]) => { const headers = new HttpHeaders({ Authorization: authToken }); const formData = new FormData(); formData.append('file', rawFile); From 08db0d5c7e69adc18aeb8f57cfdc6c05bd76a892 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Sun, 16 Aug 2020 17:28:48 +0800 Subject: [PATCH 12/31] WIP: Implement numbers storage publisher. --- src/app/app.component.ts | 13 ++++++- .../numbers-storage/numbers-storage.page.ts | 4 +-- .../numbers-storage-api.service.ts | 19 ++++++---- .../numbers-storage-publisher.ts | 35 +++++++++++++++---- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 98e0af330..70cf327ad 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -6,7 +6,9 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { CollectorService } from './services/collector/collector.service'; import { CapacitorProvider } from './services/collector/information/capacitor-provider/capacitor-provider'; import { DefaultSignatureProvider } from './services/collector/signature/default-provider/default-provider'; +import { CaptionRepository } from './services/data/caption/caption-repository.service'; import { InformationRepository } from './services/data/information/information-repository.service'; +import { ProofRepository } from './services/data/proof/proof-repository.service'; import { SignatureRepository } from './services/data/signature/signature-repository.service'; import { LanguageService } from './services/language/language.service'; import { NotificationService } from './services/notification/notification.service'; @@ -29,8 +31,10 @@ export class AppComponent { private readonly collectorService: CollectorService, private readonly publishersAlert: PublishersAlert, private readonly serializationService: SerializationService, + private readonly proofRepository: ProofRepository, private readonly informationRepository: InformationRepository, private readonly signatureRepository: SignatureRepository, + private readonly captionRepository: CaptionRepository, private readonly translocoService: TranslocoService, private readonly notificationService: NotificationService, private readonly numbersStorageApi: NumbersStorageApi, @@ -60,7 +64,14 @@ export class AppComponent { initializePublisher() { this.publishersAlert.addPublisher( - new NumbersStoragePublisher(this.translocoService, this.notificationService, this.numbersStorageApi) + new NumbersStoragePublisher( + this.translocoService, + this.notificationService, + this.proofRepository, + this.informationRepository, + this.signatureRepository, + this.captionRepository, + this.numbersStorageApi) ); } } diff --git a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts index 2600d33e5..7149381d3 100644 --- a/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts +++ b/src/app/pages/publishers/numbers-storage/numbers-storage.page.ts @@ -17,8 +17,8 @@ import { NumbersStorageApi } from 'src/app/services/publisher/numbers-storage/nu export class NumbersStoragePage { readonly isEnabled$ = this.numbersStorageApi.isEnabled$(); - readonly userName$ = this.numbersStorageApi.userName$; - readonly email$ = this.numbersStorageApi.email$; + readonly userName$ = this.numbersStorageApi.getUserName$(); + readonly email$ = this.numbersStorageApi.getEmail$(); constructor( private readonly router: Router, diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts index caf57a424..ff4afb894 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -1,11 +1,15 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { zip } from 'rxjs'; -import { concatMap, concatMapTo, map, pluck } from 'rxjs/operators'; +import { of, zip } from 'rxjs'; +import { concatMap, concatMapTo, first, map, pluck } from 'rxjs/operators'; import { base64ToBlob$ } from 'src/app/utils/encoding/encoding'; import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; import { baseUrl } from './secret'; +export const enum TargetProvider { + Numbers = 'Numbers' +} + const preference = PreferenceManager.NUMBERS_STORAGE_PUBLISHER_PREF; const enum PrefKeys { Enabled = 'enabled', @@ -27,11 +31,11 @@ export class NumbersStorageApi { return preference.getBoolean$(PrefKeys.Enabled); } - get userName$() { + getUserName$() { return preference.getString$(PrefKeys.UserName); } - get email$() { + getEmail$() { return preference.getString$(PrefKeys.Email); } @@ -68,6 +72,7 @@ export class NumbersStorageApi { getUserInformation$() { return preference.getString$(PrefKeys.AuthToken).pipe( + first(), map(authToken => new HttpHeaders({ Authorization: authToken })), concatMap(headers => this.httpClient.get(`${baseUrl}/auth/users/me/`, { headers })) ); @@ -76,6 +81,7 @@ export class NumbersStorageApi { logout$() { return preference.setBoolean$(PrefKeys.Enabled, false).pipe( concatMapTo(preference.getString$(PrefKeys.AuthToken)), + first(), map(authToken => new HttpHeaders({ Authorization: authToken })), concatMap(headers => this.httpClient.post(`${baseUrl}/auth/token/logout/`, new FormData(), { headers })) ); @@ -84,13 +90,14 @@ export class NumbersStorageApi { createMedia$( rawFileBase64: string, information: string, - targetProvider: string, + targetProvider: TargetProvider, caption: string, signatures: string, tag: string ) { return preference.getString$(PrefKeys.AuthToken).pipe( - concatMap(headers => zip(base64ToBlob$(rawFileBase64), headers)), + first(), + concatMap(authToken => zip(base64ToBlob$(rawFileBase64), of(authToken))), concatMap(([rawFile, authToken]) => { const headers = new HttpHeaders({ Authorization: authToken }); const formData = new FormData(); diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts index 476c3121f..7bd5e8c84 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts @@ -1,10 +1,14 @@ import { TranslocoService } from '@ngneat/transloco'; -import { Observable, of } from 'rxjs'; -import { delay, tap } from 'rxjs/operators'; +import { Observable, zip } from 'rxjs'; +import { concatMap, first, mapTo, tap } from 'rxjs/operators'; +import { CaptionRepository } from '../../data/caption/caption-repository.service'; +import { InformationRepository } from '../../data/information/information-repository.service'; import { Proof } from '../../data/proof/proof'; +import { ProofRepository } from '../../data/proof/proof-repository.service'; +import { SignatureRepository } from '../../data/signature/signature-repository.service'; import { NotificationService } from '../../notification/notification.service'; import { Publisher } from '../publisher'; -import { NumbersStorageApi } from './numbers-storage-api.service'; +import { NumbersStorageApi, TargetProvider } from './numbers-storage-api.service'; export class NumbersStoragePublisher extends Publisher { @@ -13,6 +17,10 @@ export class NumbersStoragePublisher extends Publisher { constructor( translocoService: TranslocoService, notificationService: NotificationService, + private readonly proofRepository: ProofRepository, + private readonly informationRepository: InformationRepository, + private readonly signatureRepository: SignatureRepository, + private readonly captionRepository: CaptionRepository, private readonly numbersStorageApi: NumbersStorageApi ) { super(translocoService, notificationService); @@ -23,10 +31,23 @@ export class NumbersStoragePublisher extends Publisher { } run$(proof: Proof): Observable { - return of(void 0).pipe( - tap(_ => console.log(`Start publishing ${proof.hash} from ${this.name}.`)), - delay(3000), - tap(_ => console.log(`Finish publishing ${proof.hash} from ${this.name}.`)) + return zip( + this.proofRepository.getRawFile$(proof), + this.informationRepository.getByProof$(proof), + this.signatureRepository.getByProof$(proof), + this.captionRepository.getByProof$(proof), + ).pipe( + first(), + tap(v => console.log(v)), + concatMap(([rawFileBase64, information, signatures, captions]) => this.numbersStorageApi.createMedia$( + `data:${proof.mimeType.type};base64,${rawFileBase64}`, + JSON.stringify(information), + TargetProvider.Numbers, + JSON.stringify(captions[0]), + JSON.stringify(signatures), + 'capture-lite' + )), + mapTo(void 0) ); } } From d1777e23f86ad2c176495d2d4fcef49694d8a347 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Tue, 18 Aug 2020 15:19:35 +0800 Subject: [PATCH 13/31] Follow the acceptable meta format of Numbers Storage backend. --- src/app/app.component.ts | 4 ++-- .../numbers-storage-api.service.ts | 21 +++++++++++++------ .../numbers-storage-publisher.ts | 12 ++++------- .../serialization/serialization.service.ts | 18 ++++++++-------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 70cf327ad..270ee1e61 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -68,10 +68,10 @@ export class AppComponent { this.translocoService, this.notificationService, this.proofRepository, - this.informationRepository, this.signatureRepository, this.captionRepository, - this.numbersStorageApi) + this.numbersStorageApi + ) ); } } diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts index ff4afb894..4977e6a4d 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -4,6 +4,9 @@ import { of, zip } from 'rxjs'; import { concatMap, concatMapTo, first, map, pluck } from 'rxjs/operators'; import { base64ToBlob$ } from 'src/app/utils/encoding/encoding'; import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; +import { Proof } from '../../data/proof/proof'; +import { Signature } from '../../data/signature/signature'; +import { SerializationService } from '../../serialization/serialization.service'; import { baseUrl } from './secret'; export const enum TargetProvider { @@ -24,7 +27,8 @@ const enum PrefKeys { export class NumbersStorageApi { constructor( - private readonly httpClient: HttpClient + private readonly httpClient: HttpClient, + private readonly serializationService: SerializationService ) { } isEnabled$() { @@ -89,23 +93,28 @@ export class NumbersStorageApi { createMedia$( rawFileBase64: string, - information: string, + proof: Proof, targetProvider: TargetProvider, caption: string, - signatures: string, + signatures: Signature[], tag: string ) { return preference.getString$(PrefKeys.AuthToken).pipe( first(), - concatMap(authToken => zip(base64ToBlob$(rawFileBase64), of(authToken))), - concatMap(([rawFile, authToken]) => { + concatMap(authToken => zip( + base64ToBlob$(rawFileBase64), + this.serializationService.stringify$(proof), + of(authToken) + )), + concatMap(([rawFile, information, authToken]) => { const headers = new HttpHeaders({ Authorization: authToken }); const formData = new FormData(); formData.append('file', rawFile); + console.log(information); formData.append('meta', information); formData.append('target_provider', targetProvider); formData.append('caption', caption); - formData.append('signature', signatures); + formData.append('signature', JSON.stringify(signatures)); formData.append('tag', tag); return this.httpClient.post(`${baseUrl}/api/v1/media/`, formData, { headers }); }) diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts index fd1d7a09f..3becd1b7f 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-publisher.ts @@ -1,8 +1,7 @@ import { TranslocoService } from '@ngneat/transloco'; import { Observable, zip } from 'rxjs'; -import { concatMap, first, mapTo, tap } from 'rxjs/operators'; +import { concatMap, first, mapTo } from 'rxjs/operators'; import { CaptionRepository } from '../../data/caption/caption-repository.service'; -import { InformationRepository } from '../../data/information/information-repository.service'; import { Proof } from '../../data/proof/proof'; import { ProofRepository } from '../../data/proof/proof-repository.service'; import { SignatureRepository } from '../../data/signature/signature-repository.service'; @@ -18,7 +17,6 @@ export class NumbersStoragePublisher extends Publisher { translocoService: TranslocoService, notificationService: NotificationService, private readonly proofRepository: ProofRepository, - private readonly informationRepository: InformationRepository, private readonly signatureRepository: SignatureRepository, private readonly captionRepository: CaptionRepository, private readonly numbersStorageApi: NumbersStorageApi @@ -33,18 +31,16 @@ export class NumbersStoragePublisher extends Publisher { run$(proof: Proof): Observable { return zip( this.proofRepository.getRawFile$(proof), - this.informationRepository.getByProof$(proof), this.signatureRepository.getByProof$(proof), this.captionRepository.getByProof$(proof), ).pipe( first(), - tap(v => console.log(v)), - concatMap(([rawFileBase64, information, signatures, caption]) => this.numbersStorageApi.createMedia$( + concatMap(([rawFileBase64, signatures, caption]) => this.numbersStorageApi.createMedia$( `data:${proof.mimeType.type};base64,${rawFileBase64}`, - JSON.stringify(information), + proof, TargetProvider.Numbers, JSON.stringify(caption ? caption : ''), - JSON.stringify(signatures), + signatures, 'capture-lite' )), mapTo(void 0) diff --git a/src/app/services/serialization/serialization.service.ts b/src/app/services/serialization/serialization.service.ts index 22780f94c..3613c5fbf 100644 --- a/src/app/services/serialization/serialization.service.ts +++ b/src/app/services/serialization/serialization.service.ts @@ -4,6 +4,13 @@ import { Information } from '../data/information/information'; import { InformationRepository } from '../data/information/information-repository.service'; import { Proof } from '../data/proof/proof'; +type EssentialInformation = Pick; + +interface SortedProofInformation { + readonly proof: Proof; + readonly information: EssentialInformation[]; +} + @Injectable({ providedIn: 'root' }) @@ -24,22 +31,15 @@ export class SerializationService { first(), map(informationList => { const sortedInformation = informationList.sort((a: Information, b: Information) => { - const proofHashCompared = a.proofHash.localeCompare(b.proofHash); const providerCompared = a.provider.localeCompare(b.provider); const nameCompared = a.name.localeCompare(b.name); const valueCompared = a.value.localeCompare(b.value); - if (proofHashCompared !== 0) { return proofHashCompared; } if (providerCompared !== 0) { return providerCompared; } if (nameCompared !== 0) { return nameCompared; } return valueCompared; - }); - return ({ proof, sortedInformation } as SortedProofInformation); + }).map(({ provider, name, value }) => ({ provider, name, value } as EssentialInformation)); + return ({ proof, information: sortedInformation } as SortedProofInformation); }) ); } } - -interface SortedProofInformation { - readonly proof: Proof; - readonly sortedInformation: Information[]; -} From 88719ba4c3328ed0f7efdc6ee5ce817d2409887e Mon Sep 17 00:00:00 2001 From: DaYuan Date: Sat, 3 Oct 2020 03:20:49 +0800 Subject: [PATCH 14/31] add svg --- src/assets/Capture.svg | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/assets/Capture.svg diff --git a/src/assets/Capture.svg b/src/assets/Capture.svg new file mode 100644 index 000000000..5bfcb70b6 --- /dev/null +++ b/src/assets/Capture.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + From 3cee1f3ea5c3eb7187f2273e7b1a92c17a57091a Mon Sep 17 00:00:00 2001 From: DaYuan Date: Sat, 3 Oct 2020 03:24:15 +0800 Subject: [PATCH 15/31] add login page --- src/app/pages/login/login-routing.module.ts | 17 ++++++ src/app/pages/login/login.module.ts | 20 +++++++ src/app/pages/login/login.page.html | 36 ++++++++++++ src/app/pages/login/login.page.scss | 64 +++++++++++++++++++++ src/app/pages/login/login.page.spec.ts | 24 ++++++++ src/app/pages/login/login.page.ts | 15 +++++ 6 files changed, 176 insertions(+) create mode 100644 src/app/pages/login/login-routing.module.ts create mode 100644 src/app/pages/login/login.module.ts create mode 100644 src/app/pages/login/login.page.html create mode 100644 src/app/pages/login/login.page.scss create mode 100644 src/app/pages/login/login.page.spec.ts create mode 100644 src/app/pages/login/login.page.ts diff --git a/src/app/pages/login/login-routing.module.ts b/src/app/pages/login/login-routing.module.ts new file mode 100644 index 000000000..29ef3a236 --- /dev/null +++ b/src/app/pages/login/login-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { LoginPage } from './login.page'; + +const routes: Routes = [ + { + path: '', + component: LoginPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class LoginPageRoutingModule {} diff --git a/src/app/pages/login/login.module.ts b/src/app/pages/login/login.module.ts new file mode 100644 index 000000000..5eda226d5 --- /dev/null +++ b/src/app/pages/login/login.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { LoginPageRoutingModule } from './login-routing.module'; + +import { LoginPage } from './login.page'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + LoginPageRoutingModule + ], + declarations: [LoginPage] +}) +export class LoginPageModule {} diff --git a/src/app/pages/login/login.page.html b/src/app/pages/login/login.page.html new file mode 100644 index 000000000..7226e9a0b --- /dev/null +++ b/src/app/pages/login/login.page.html @@ -0,0 +1,36 @@ + + + + + + + +
+ +
+
+ +
+
+
+ + + Bring Trust Into Data + + + + + + LOGIN + + + + + CREATE AN ACCOUNT? + + + +
+
\ No newline at end of file diff --git a/src/app/pages/login/login.page.scss b/src/app/pages/login/login.page.scss new file mode 100644 index 000000000..7dd54d881 --- /dev/null +++ b/src/app/pages/login/login.page.scss @@ -0,0 +1,64 @@ + +ion-grid { + height: 100%; + background: #fff!important; +} +ion-button { + width: calc(100vw - var(--ion-padding, 23px) ); + padding-left: 23; + padding-right: 23; + --border-radius: 50px!important; + --height: 50px !important; + --background: #564DFC!important; +} +ion-title { + width: 100%; +} +ion-input{ + width: calc(100vw - var(--ion-padding, 23px) ); + text-align: left; + border-color: gray!important; + border-width: 10px; + margin-bottom: 10px!important; + color: #000; +} +.input{ + width: calc(100vw - var(--ion-padding, 23px) )!important; + border-color: #564DFC!important; + text-align: left; + border-width: 10px!important; + border-bottom: 10px!important; + padding-left: 10px!important; + padding-right: 10px!important; +} +.img { + --height: 127.15px!important; +} +.logo { + width: 100%; + display: flex; + flex: 1; + height: 35vh; + justify-content: center; + align-items: center; + justify-content: center; + align-items: center; +} +.inputs { + width: 100%!important; + display: flex; + flex: 1; + height: 35vh; +} +.buttons { + width: 100%!important; + display: flex; + flex: 1; + bottom: 0px; +} +ion-content { + display: flex; + flex: 1; + background: #333!important; + height: 100%; +} \ No newline at end of file diff --git a/src/app/pages/login/login.page.spec.ts b/src/app/pages/login/login.page.spec.ts new file mode 100644 index 000000000..07cb76a93 --- /dev/null +++ b/src/app/pages/login/login.page.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { LoginPage } from './login.page'; + +describe('LoginPage', () => { + let component: LoginPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoginPage ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(LoginPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/login/login.page.ts b/src/app/pages/login/login.page.ts new file mode 100644 index 000000000..141f59e48 --- /dev/null +++ b/src/app/pages/login/login.page.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-login', + templateUrl: './login.page.html', + styleUrls: ['./login.page.scss'], +}) +export class LoginPage implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} From 235b7c533d4758da6dc1890f4138c2f50cfef6fc Mon Sep 17 00:00:00 2001 From: DaYuan Date: Sat, 3 Oct 2020 03:24:36 +0800 Subject: [PATCH 16/31] add signup page --- src/app/pages/signup/signup-routing.module.ts | 17 ++++ src/app/pages/signup/signup.module.ts | 20 +++++ src/app/pages/signup/signup.page.html | 39 ++++++++++ src/app/pages/signup/signup.page.scss | 78 +++++++++++++++++++ src/app/pages/signup/signup.page.spec.ts | 24 ++++++ src/app/pages/signup/signup.page.ts | 15 ++++ 6 files changed, 193 insertions(+) create mode 100644 src/app/pages/signup/signup-routing.module.ts create mode 100644 src/app/pages/signup/signup.module.ts create mode 100644 src/app/pages/signup/signup.page.html create mode 100644 src/app/pages/signup/signup.page.scss create mode 100644 src/app/pages/signup/signup.page.spec.ts create mode 100644 src/app/pages/signup/signup.page.ts diff --git a/src/app/pages/signup/signup-routing.module.ts b/src/app/pages/signup/signup-routing.module.ts new file mode 100644 index 000000000..d1ee57861 --- /dev/null +++ b/src/app/pages/signup/signup-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SignupPage } from './signup.page'; + +const routes: Routes = [ + { + path: '', + component: SignupPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SignupPageRoutingModule {} diff --git a/src/app/pages/signup/signup.module.ts b/src/app/pages/signup/signup.module.ts new file mode 100644 index 000000000..295034bd2 --- /dev/null +++ b/src/app/pages/signup/signup.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { SignupPageRoutingModule } from './signup-routing.module'; + +import { SignupPage } from './signup.page'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + SignupPageRoutingModule + ], + declarations: [SignupPage] +}) +export class SignupPageModule {} diff --git a/src/app/pages/signup/signup.page.html b/src/app/pages/signup/signup.page.html new file mode 100644 index 000000000..4951655f3 --- /dev/null +++ b/src/app/pages/signup/signup.page.html @@ -0,0 +1,39 @@ + + + + + + + +
+ +
+
+ +
+
+ +
+
+
+ + + Bring Trust Into Data + + + + + + + + + ALREADY HAVE AN ACCOUNT? + + + +
+
\ No newline at end of file diff --git a/src/app/pages/signup/signup.page.scss b/src/app/pages/signup/signup.page.scss new file mode 100644 index 000000000..fb9de6142 --- /dev/null +++ b/src/app/pages/signup/signup.page.scss @@ -0,0 +1,78 @@ + +ion-grid { + height: 100%; + background: #fff!important; + } +ion-button { + width: calc(100vw - var(--ion-padding, 23px) ); + padding-left: 23; + padding-right: 23; + --border-radius: 50px!important; + --height: 50px !important; + --background: #564DFC; + } +.SIGNUP{ + --background: #EEEEEE!important; + color: #000; +} +ion-title { + width: 100%; +} +ion-input{ + color: #000; + width: calc(100vw - var(--ion-padding, 23px) ); + text-align: left; + border-color: gray!important; + border-width: 10px; + margin-bottom: 10px!important; + border-width: 10px!important; + border-bottom: 10px!important; +} +.native-input.sc-ion-input-ios { + text-align: left; + border-color: gray!important; + border-width: 10px; + margin-bottom: 10px!important; + border-width: 10px!important; + border-bottom: 10px!important; +} +.input{ + width: calc(100vw - var(--ion-padding, 23px) )!important; + border-color: #564DFC!important; + text-align: left; + border-width: 10px!important; + border-bottom: 10px!important; + padding-left: 10px!important; + padding-right: 10px!important; +} +.img { + --height: 127.15px!important; +} +.logo { + width: 100%; + display: flex; + flex: 1; + height: 35vh; +justify-content: center; + align-items: center; + justify-content: center; + align-items: center; +} +.inputs { + width: 100%!important; + display: flex; + flex: 1; + height: 35vh; +} +.buttons { + width: 100%!important; + display: flex; + flex: 1; + bottom: 0px; +} +ion-content { + display: flex; + flex: 1; + background: #333!important; + height: 100%; +} \ No newline at end of file diff --git a/src/app/pages/signup/signup.page.spec.ts b/src/app/pages/signup/signup.page.spec.ts new file mode 100644 index 000000000..1b0c3b114 --- /dev/null +++ b/src/app/pages/signup/signup.page.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { SignupPage } from './signup.page'; + +describe('SignupPage', () => { + let component: SignupPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SignupPage ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(SignupPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/signup/signup.page.ts b/src/app/pages/signup/signup.page.ts new file mode 100644 index 000000000..34a3c3895 --- /dev/null +++ b/src/app/pages/signup/signup.page.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-signup', + templateUrl: './signup.page.html', + styleUrls: ['./signup.page.scss'], +}) +export class SignupPage implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} From aea327eac298edfa196de6b18540833fea93acfc Mon Sep 17 00:00:00 2001 From: DaYuan Date: Sat, 3 Oct 2020 03:24:55 +0800 Subject: [PATCH 17/31] add page in routing --- src/app/app-routing.module.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index caa16b125..280371ec8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,9 +3,19 @@ import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; const routes: Routes = [{ path: '', - redirectTo: 'storage', + redirectTo: 'login', + // redirectTo: 'storage', pathMatch: 'full' }, +{ + path: 'login', + loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule) +}, +{ + path: 'signup', + loadChildren: () => import('./pages/signup/signup.module').then(m => m.SignupPageModule) +}, + { path: 'storage', loadChildren: () => import('./pages/storage/storage.module').then(m => m.StoragePageModule) @@ -38,7 +48,8 @@ const routes: Routes = [{ }, { path: 'about', loadChildren: () => import('./pages/about/about.module').then(m => m.AboutPageModule) -}]; +}, +]; @NgModule({ imports: [ From 283190054fbeabd20464adc7675684dac536b7d6 Mon Sep 17 00:00:00 2001 From: DaYuan Date: Sun, 4 Oct 2020 02:42:03 +0800 Subject: [PATCH 18/31] add i18n --- src/app/pages/login/login.module.ts | 9 ++++----- src/app/pages/login/login.page.html | 12 ++++++------ src/app/pages/login/login.page.scss | 4 +++- src/app/pages/signup/signup.module.ts | 12 +++++++----- src/app/pages/signup/signup.page.html | 14 +++++++------- src/app/pages/signup/signup.page.scss | 4 +++- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/app/pages/login/login.module.ts b/src/app/pages/login/login.module.ts index 5eda226d5..64e929475 100644 --- a/src/app/pages/login/login.module.ts +++ b/src/app/pages/login/login.module.ts @@ -1,15 +1,14 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; - import { IonicModule } from '@ionic/angular'; - +import { TranslocoModule } from '@ngneat/transloco'; import { LoginPageRoutingModule } from './login-routing.module'; - import { LoginPage } from './login.page'; @NgModule({ imports: [ + TranslocoModule, CommonModule, FormsModule, IonicModule, @@ -17,4 +16,4 @@ import { LoginPage } from './login.page'; ], declarations: [LoginPage] }) -export class LoginPageModule {} +export class LoginPageModule { } diff --git a/src/app/pages/login/login.page.html b/src/app/pages/login/login.page.html index 7226e9a0b..ca7852523 100644 --- a/src/app/pages/login/login.page.html +++ b/src/app/pages/login/login.page.html @@ -1,4 +1,4 @@ - +
{{ t('message.PleaseCheckYourEmail') }}
- - -
- -
-
- -
-
- -
-
-
- - - {{ t('bringTrustIntoData') }} - - - - - - - - - {{ t('alreadyHaveAnAccount') }} - - - +
+ + + + + + + + {{ t('bringTrustIntoData') }} + + + + + + + + + {{ t('alreadyHaveAnAccount') }} + + + +
\ No newline at end of file diff --git a/src/app/pages/signup/signup.page.ts b/src/app/pages/signup/signup.page.ts index 34a3c3895..53d889795 100644 --- a/src/app/pages/signup/signup.page.ts +++ b/src/app/pages/signup/signup.page.ts @@ -1,5 +1,13 @@ import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { combineLatest } from 'rxjs'; + +import { TranslocoService } from '@ngneat/transloco'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { FormlyFieldConfig } from '@ngx-formly/core'; + +@UntilDestroy() @Component({ selector: 'app-signup', templateUrl: './signup.page.html', @@ -7,9 +15,80 @@ import { Component, OnInit } from '@angular/core'; }) export class SignupPage implements OnInit { - constructor() { } + form = new FormGroup({}); + model = {}; + fields: FormlyFieldConfig[] = []; + formInitialized = false; + + constructor( + private readonly translocoService: TranslocoService, + ) { } ngOnInit() { + combineLatest([ + this.translocoService.selectTranslate('email'), + this.translocoService.selectTranslate('password'), + this.translocoService.selectTranslate('repeatPassword'), + ]).pipe( + untilDestroyed(this), + ).subscribe( + translations => { + this.fields = this.createFormFields(translations); + this.formInitialized = true; + } + ); + } + + onSubmit() { + console.log(this.model); + } + + private createFormFields(translations: string[]): FormlyFieldConfig[] { + const [emailTranslation, passwordTranslation, repeatPasswordTranslation] = translations; + const fields: FormlyFieldConfig[] = [{ + validators: { + validation: [ + { name: 'fieldMatch', options: { errorPath: 'repeatPassword' } }, + ], + }, + fieldGroup: [ + { + key: 'email', + type: 'input', + templateOptions: { + type: 'email', + placeholder: emailTranslation, + required: true, + hideRequiredMarker: true, + pattern: /(.+)@(.+){2,}\.(.+){2,}/, + } + }, + { + key: 'password', + type: 'input', + templateOptions: { + type: 'password', + placeholder: passwordTranslation, + required: true, + hideRequiredMarker: true, + minLength: 8, + maxLength: 32, + } + }, + { + key: 'repeatPassword', + type: 'input', + templateOptions: { + type: 'password', + placeholder: repeatPasswordTranslation, + required: true, + hideRequiredMarker: true, + } + }, + ] + } + ]; + return fields; } } diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index b76caec49..306b39061 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -80,7 +80,9 @@ "verificationEmailSent": "A verification email has been sent to your email address.", "pleaseCheckYourEmail": "Please check your email inbox and follow the instructions in the email to activate your account.", "emailAlreadyExists": "An Account with this email already exists.", - "passwordMustBeBetween": "Password must be between {{min}} to {{max} characters long.", + "pleaseEnterValidEmail": "Please enter a valid email address", + "passwordMustBeBetween": "Password must be between {{min}} to {{max}} characters.", + "passwordNotMatching": "Password not matching.", "accountNotActivated": "This account has not been activated yet.", "ifYouNotReceiveEmail": "If you haven't received the verification email in 10 minutes, you can click the button below to resend the verification email.", "emailOrPasswordIsInvalid": "The email address or password you entered is invalid", From a00b3f8003c9178212b028bf3529a8176ed12cc5 Mon Sep 17 00:00:00 2001 From: DaYuan Date: Mon, 12 Oct 2020 15:11:17 +0800 Subject: [PATCH 21/31] add in [disabled]="true" set ion-btn bgColor --- src/app/pages/login/login.page.scss | 6 ++++++ src/app/pages/signup/signup.page.scss | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/app/pages/login/login.page.scss b/src/app/pages/login/login.page.scss index d75442066..776ca1b51 100644 --- a/src/app/pages/login/login.page.scss +++ b/src/app/pages/login/login.page.scss @@ -6,12 +6,18 @@ ion-grid { background: #fff!important; } ion-button { + &[disabled] { + opacity: 1; + --background: #EEEEEE!important; + color: #000; + } width: calc(100vw - var(--ion-padding, 23px) ); padding-left: 23; padding-right: 23; --border-radius: 50px!important; --height: 50px !important; --background: #564DFC!important; + } ion-title { width: 100%; diff --git a/src/app/pages/signup/signup.page.scss b/src/app/pages/signup/signup.page.scss index 96dd941d3..5ec001647 100644 --- a/src/app/pages/signup/signup.page.scss +++ b/src/app/pages/signup/signup.page.scss @@ -6,6 +6,11 @@ ion-grid { background: #fff!important; } ion-button { + &[disabled] { + opacity: 1; + --background: #EEEEEE!important; + color: #000; + } width: calc(100vw - var(--ion-padding, 23px) ); padding-left: 23; padding-right: 23; From 932e3a26ce05ae93eab34b7621d1973f98545656 Mon Sep 17 00:00:00 2001 From: James Chien Date: Mon, 12 Oct 2020 15:36:44 +0800 Subject: [PATCH 22/31] Disable tslint non-null-keyword for Validator accepts ValidatorError|null Signed-off-by: James Chien --- src/app/pages/signup/signup.module.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/pages/signup/signup.module.ts b/src/app/pages/signup/signup.module.ts index 0ffb4ca95..bb1f6589e 100644 --- a/src/app/pages/signup/signup.module.ts +++ b/src/app/pages/signup/signup.module.ts @@ -14,16 +14,18 @@ import { FormlyIonicModule } from '@ngx-formly/ionic'; import { SignupPageRoutingModule } from './signup-routing.module'; import { SignupPage } from './signup.page'; -export function fieldMatchValidator(control: AbstractControl, translocoService: TranslocoService) { +export function fieldMatchValidator(control: AbstractControl) { const { password, repeatPassword } = control.value; // avoid displaying the message error when values are empty if (!repeatPassword || !password) { - return undefined; + // tslint:disable-next-line: no-null-keyword + return null; } if (repeatPassword === password) { - return undefined; + // tslint:disable-next-line: no-null-keyword + return null; } return { fieldMatch: true }; From a7e336d0e7900beefcf243b67c2b53e425fd5114 Mon Sep 17 00:00:00 2001 From: James Chien Date: Mon, 12 Oct 2020 19:04:55 +0800 Subject: [PATCH 23/31] Add toast message & loading Signed-off-by: James Chien --- src/app/pages/login/login.page.html | 3 +- src/app/pages/login/login.page.scss | 52 ++++++++++---------- src/app/pages/login/login.page.ts | 42 ++++++++++++++-- src/app/pages/signup/signup.page.html | 5 +- src/app/pages/signup/signup.page.scss | 69 +++++++++++++-------------- src/app/pages/signup/signup.page.ts | 59 +++++++++++++++++++++-- src/assets/i18n/en-us.json | 5 +- 7 files changed, 156 insertions(+), 79 deletions(-) diff --git a/src/app/pages/login/login.page.html b/src/app/pages/login/login.page.html index e71fcf0fe..7eba3d5f3 100644 --- a/src/app/pages/login/login.page.html +++ b/src/app/pages/login/login.page.html @@ -5,11 +5,10 @@ -
{{ t('message.PleaseCheckYourEmail') }}
- + diff --git a/src/app/pages/login/login.page.scss b/src/app/pages/login/login.page.scss index 776ca1b51..daafa53a5 100644 --- a/src/app/pages/login/login.page.scss +++ b/src/app/pages/login/login.page.scss @@ -2,40 +2,38 @@ ion-title { color: #333; } ion-grid { - height: 100%; - background: #fff!important; + height: 100%; + background: #fff !important; } ion-button { &[disabled] { opacity: 1; - --background: #EEEEEE!important; + --background: #eeeeee !important; color: #000; } - width: calc(100vw - var(--ion-padding, 23px) ); + width: calc(100vw - var(--ion-padding, 23px)); padding-left: 23; padding-right: 23; - --border-radius: 50px!important; + --border-radius: 50px !important; --height: 50px !important; - --background: #564DFC!important; - + --background: #564dfc !important; } ion-title { width: 100%; } -ion-input{ - width: calc(100vw - var(--ion-padding, 23px) ); +ion-input { + width: calc(100vw - var(--ion-padding, 23px)); text-align: left; - border-color: gray!important; + border-color: gray !important; border-width: 10px; - margin-bottom: 10px!important; + margin-bottom: 10px !important; color: #000; } -.input_msg{ +.input_msg { width: 100%; - height: 20px; - color: red!important; + height: 40px; + color: red !important; text-align: center; - } .input_R { text-align: right; @@ -43,17 +41,17 @@ ion-input{ .input_C { text-align: center; } -.input{ - width: calc(100vw - var(--ion-padding, 23px) )!important; - border-color: #564DFC!important; +.input { + width: calc(100vw - var(--ion-padding, 23px)) !important; + border-color: #564dfc !important; text-align: left; - border-width: 10px!important; - border-bottom: 10px!important; - padding-left: 10px!important; - padding-right: 10px!important; + border-width: 10px !important; + border-bottom: 10px !important; + padding-left: 10px !important; + padding-right: 10px !important; } .img { - --height: 127.15px!important; + --height: 127.15px !important; } .logo { width: 100%; @@ -66,13 +64,13 @@ ion-input{ align-items: center; } .inputs { - width: 100%!important; + width: 100% !important; display: flex; flex: 1; height: 35vh; } .buttons { - width: 100%!important; + width: 100% !important; display: flex; flex: 1; bottom: 0px; @@ -80,6 +78,6 @@ ion-input{ ion-content { display: flex; flex: 1; - background: #333!important; + background: #333 !important; height: 100%; -} \ No newline at end of file +} diff --git a/src/app/pages/login/login.page.ts b/src/app/pages/login/login.page.ts index e30482e72..852c02b23 100644 --- a/src/app/pages/login/login.page.ts +++ b/src/app/pages/login/login.page.ts @@ -1,12 +1,24 @@ import { Component, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; -import { combineLatest } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { + BlockingActionService, +} from 'src/app/services/blocking-action/blocking-action.service'; +import { + NumbersStorageApi, +} from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; +import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { FormlyFieldConfig } from '@ngx-formly/core'; +import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core'; +interface LoginFormModel { + email: string; + password: string; +} @UntilDestroy() @Component({ selector: 'app-login', @@ -15,12 +27,19 @@ import { FormlyFieldConfig } from '@ngx-formly/core'; }) export class LoginPage implements OnInit { form = new FormGroup({}); - model = {}; + model: LoginFormModel = { email: '', password: '' }; + options: FormlyFormOptions = {}; fields: FormlyFieldConfig[] = []; formInitialized = false; + private readonly messageSubject$ = new BehaviorSubject(''); + message$: Observable = this.messageSubject$; constructor( + private readonly blockingActionService: BlockingActionService, + private readonly numbersStorageApi: NumbersStorageApi, + private readonly toastController: ToastController, private readonly translocoService: TranslocoService, + private readonly router: Router, ) { } ngOnInit() { @@ -38,7 +57,22 @@ export class LoginPage implements OnInit { } onSubmit() { - console.log(this.model); + const action$ = this.numbersStorageApi.login$(this.model.email, this.model.password); + this.blockingActionService.run$( + action$, + { message: this.translocoService.translate('message.pleaseWait') } + ).pipe( + untilDestroyed(this), + ).subscribe( + () => this.router.navigate(['storage'], { replaceUrl: true }), + err => this.toastController + .create({ + message: this.translocoService.translate('message.emailOrPasswordIsInvalid'), + duration: 4000, + color: 'danger', + }) + .then(toast => toast.present()), + ); } private createFormFields(translations: string[]): FormlyFieldConfig[] { diff --git a/src/app/pages/signup/signup.page.html b/src/app/pages/signup/signup.page.html index 82f667961..46191c1af 100644 --- a/src/app/pages/signup/signup.page.html +++ b/src/app/pages/signup/signup.page.html @@ -5,11 +5,10 @@ -
{{ t('message.PleaseCheckYourEmail') }}
- - + + diff --git a/src/app/pages/signup/signup.page.scss b/src/app/pages/signup/signup.page.scss index 5ec001647..0218f8780 100644 --- a/src/app/pages/signup/signup.page.scss +++ b/src/app/pages/signup/signup.page.scss @@ -3,52 +3,47 @@ ion-title { } ion-grid { height: 100%; - background: #fff!important; - } + background: #fff !important; +} ion-button { &[disabled] { opacity: 1; - --background: #EEEEEE!important; + --background: #eeeeee !important; color: #000; } - width: calc(100vw - var(--ion-padding, 23px) ); + width: calc(100vw - var(--ion-padding, 23px)); padding-left: 23; padding-right: 23; - --border-radius: 50px!important; + --border-radius: 50px !important; --height: 50px !important; - --background: #564DFC; - } -.SIGNUP{ - --background: #EEEEEE!important; - color: #000; + --background: #564dfc !important; } + ion-title { width: 100%; } -ion-input{ +ion-input { color: #000; - width: calc(100vw - var(--ion-padding, 23px) ); + width: calc(100vw - var(--ion-padding, 23px)); text-align: left; - border-color: gray!important; + border-color: gray !important; border-width: 10px; - margin-bottom: 10px!important; - border-width: 10px!important; - border-bottom: 10px!important; + margin-bottom: 10px !important; + border-width: 10px !important; + border-bottom: 10px !important; } .native-input.sc-ion-input-ios { text-align: left; - border-color: gray!important; + border-color: gray !important; border-width: 10px; - margin-bottom: 10px!important; - border-width: 10px!important; - border-bottom: 10px!important; + margin-bottom: 10px !important; + border-width: 10px !important; + border-bottom: 10px !important; } -.input_msg{ +.input_msg { width: 100%; - height: 20px; - color: red!important; + height: 80px; text-align: center; - } .input_R { text-align: right; @@ -56,36 +51,36 @@ ion-input{ .input_C { text-align: center; } -.input{ - width: calc(100vw - var(--ion-padding, 23px) )!important; - border-color: #564DFC!important; +.input { + width: calc(100vw - var(--ion-padding, 23px)) !important; + border-color: #564dfc !important; text-align: left; - border-width: 10px!important; - border-bottom: 10px!important; - padding-left: 10px!important; - padding-right: 10px!important; + border-width: 10px !important; + border-bottom: 10px !important; + padding-left: 10px !important; + padding-right: 10px !important; } .img { - --height: 127.15px!important; + --height: 127.15px !important; } .logo { width: 100%; display: flex; flex: 1; height: 35vh; -justify-content: center; + justify-content: center; align-items: center; justify-content: center; align-items: center; } .inputs { - width: 100%!important; + width: 100% !important; display: flex; flex: 1; height: 35vh; } .buttons { - width: 100%!important; + width: 100% !important; display: flex; flex: 1; bottom: 0px; @@ -93,6 +88,6 @@ justify-content: center; ion-content { display: flex; flex: 1; - background: #333!important; + background: #333 !important; height: 100%; -} \ No newline at end of file +} diff --git a/src/app/pages/signup/signup.page.ts b/src/app/pages/signup/signup.page.ts index 53d889795..dfcfffe38 100644 --- a/src/app/pages/signup/signup.page.ts +++ b/src/app/pages/signup/signup.page.ts @@ -1,11 +1,29 @@ import { Component, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { combineLatest } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { + BlockingActionService, +} from 'src/app/services/blocking-action/blocking-action.service'; +import { + NumbersStorageApi, +} from 'src/app/services/publisher/numbers-storage/numbers-storage-api.service'; +import { ToastController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { FormlyFieldConfig } from '@ngx-formly/core'; +import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core'; + +interface LoginFormModel { + email: string; + password: string; + repeatPassword: string; +} + +interface MessageConfig { + color: string; + text: string; +} @UntilDestroy() @Component({ @@ -16,11 +34,17 @@ import { FormlyFieldConfig } from '@ngx-formly/core'; export class SignupPage implements OnInit { form = new FormGroup({}); - model = {}; + model = { email: '', password: '', repeatPassword: '' }; + options: FormlyFormOptions = {}; fields: FormlyFieldConfig[] = []; formInitialized = false; + private readonly messageConfigSubject$ = new BehaviorSubject({ color: '', text: '' }); + messageConfig$: Observable = this.messageConfigSubject$; constructor( + private readonly blockingActionService: BlockingActionService, + private readonly numbersStorageApi: NumbersStorageApi, + private readonly toastController: ToastController, private readonly translocoService: TranslocoService, ) { } @@ -40,7 +64,34 @@ export class SignupPage implements OnInit { } onSubmit() { - console.log(this.model); + const defaultUsername = ''; + const action$ = this.numbersStorageApi.createUser$(defaultUsername, this.model.email, this.model.password); + + this.blockingActionService.run$( + action$, + { message: this.translocoService.translate('message.pleaseWait') } + ).pipe( + untilDestroyed(this), + ).subscribe( + () => { + this.messageConfigSubject$.next({ + color: 'primary', + text: + this.translocoService.translate('message.verificationEmailSent') + + this.translocoService.translate('message.pleaseCheckYourEmail') + }); + if (this?.options?.resetModel) { + this.options.resetModel(); + } + }, + // FIXME: The actual error type can't be determined from response. Fix this after API updates error messages. + err => this.toastController + .create({ + message: this.translocoService.translate('message.emailAlreadyExists'), + duration: 4000, + color: 'danger', + }) + .then(toast => toast.present())); } private createFormFields(translations: string[]): FormlyFieldConfig[] { diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 306b39061..2210ff3f9 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -85,11 +85,12 @@ "passwordNotMatching": "Password not matching.", "accountNotActivated": "This account has not been activated yet.", "ifYouNotReceiveEmail": "If you haven't received the verification email in 10 minutes, you can click the button below to resend the verification email.", - "emailOrPasswordIsInvalid": "The email address or password you entered is invalid", + "emailOrPasswordIsInvalid": "The email address or password you entered is invalid.", "tooManyRetries": "You have entered the invalid email or password too many times. Please try again later.", "publishingProof": "Publishing proof {{hash}} to {{publisherName}}.", "proofPublished": "Proof {{hash}} has been published to {{publisherName}}.", "forbiddenAllNumeric": "Cannot contain only numbers.", - "isNotEmail": "Does not follow email format." + "isNotEmail": "Does not follow email format.", + "pleaseWait": "Please wait..." } } \ No newline at end of file From ed7c14398b8104983eab932a2c6ac89775075af7 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 07:58:59 +0800 Subject: [PATCH 24/31] Add login auth guard & redirects Signed-off-by: James Chien --- src/app/app-routing.module.ts | 35 ++++++++++++------- .../services/auth/auth-guard.service.spec.ts | 16 +++++++++ src/app/services/auth/auth-guard.service.ts | 29 +++++++++++++++ src/app/services/auth/auth.service.spec.ts | 16 +++++++++ src/app/services/auth/auth.service.ts | 31 ++++++++++++++++ 5 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 src/app/services/auth/auth-guard.service.spec.ts create mode 100644 src/app/services/auth/auth-guard.service.ts create mode 100644 src/app/services/auth/auth.service.spec.ts create mode 100644 src/app/services/auth/auth.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 280371ec8..8c8345dfe 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,10 +1,11 @@ import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; +import { AuthGuardService } from './services/auth/auth-guard.service'; + const routes: Routes = [{ path: '', - redirectTo: 'login', - // redirectTo: 'storage', + redirectTo: 'storage', pathMatch: 'full' }, { @@ -18,36 +19,46 @@ const routes: Routes = [{ { path: 'storage', - loadChildren: () => import('./pages/storage/storage.module').then(m => m.StoragePageModule) + loadChildren: () => import('./pages/storage/storage.module').then(m => m.StoragePageModule), + canActivate: [AuthGuardService], }, { path: 'settings', - loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsPageModule) + loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsPageModule), + canActivate: [AuthGuardService], }, { path: 'proof', - loadChildren: () => import('./pages/proof/proof.module').then(m => m.ProofPageModule) + loadChildren: () => import('./pages/proof/proof.module').then(m => m.ProofPageModule), + canActivate: [AuthGuardService], }, { path: 'information', - loadChildren: () => import('./pages/information/information.module').then(m => m.InformationPageModule) + loadChildren: () => import('./pages/information/information.module').then(m => m.InformationPageModule), + canActivate: [AuthGuardService], }, { path: 'publishers', - redirectTo: 'settings' + redirectTo: 'settings', + canActivate: [AuthGuardService], }, { path: 'publishers/numbers-storage', - loadChildren: () => import('./pages/publishers/numbers-storage/numbers-storage.module').then(m => m.NumbersStoragePageModule) + loadChildren: () => import('./pages/publishers/numbers-storage/numbers-storage.module').then(m => m.NumbersStoragePageModule), + canActivate: [AuthGuardService], }, { path: 'general', - loadChildren: () => import('./pages/general/general.module').then(m => m.GeneralPageModule) + loadChildren: () => import('./pages/general/general.module').then(m => m.GeneralPageModule), + canActivate: [AuthGuardService], }, { path: 'defaultinformationprovider', // tslint:disable-next-line: max-line-length - loadChildren: () => import('./pages/defaultinformationprovider/defaultinformationprovider.module').then(m => m.DefaultInformationProviderPageModule) + loadChildren: () => import('./pages/defaultinformationprovider/defaultinformationprovider.module').then(m => m.DefaultInformationProviderPageModule), + canActivate: [AuthGuardService], }, { path: 'defaultsignature', - loadChildren: () => import('./pages/defaultsignature/defaultsignature.module').then(m => m.DefaultSignaturePageModule) + loadChildren: () => import('./pages/defaultsignature/defaultsignature.module').then(m => m.DefaultSignaturePageModule), + canActivate: [AuthGuardService], }, { path: 'about', - loadChildren: () => import('./pages/about/about.module').then(m => m.AboutPageModule) + loadChildren: () => import('./pages/about/about.module').then(m => m.AboutPageModule), + canActivate: [AuthGuardService], }, ]; diff --git a/src/app/services/auth/auth-guard.service.spec.ts b/src/app/services/auth/auth-guard.service.spec.ts new file mode 100644 index 000000000..35afd3775 --- /dev/null +++ b/src/app/services/auth/auth-guard.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthGuardService } from './auth-guard.service'; + +describe('AuthGuardService', () => { + let service: AuthGuardService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthGuardService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/auth/auth-guard.service.ts b/src/app/services/auth/auth-guard.service.ts new file mode 100644 index 000000000..4f0eaa1d2 --- /dev/null +++ b/src/app/services/auth/auth-guard.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router } from '@angular/router'; + +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuardService implements CanActivate { + + constructor( + private readonly auth: AuthService, + private readonly router: Router, + ) { } + + canActivate(): Observable { + return this.auth.isAuthenticated$() + .pipe( + tap(isAuthenticated => { + if (!isAuthenticated) { + this.router.navigate(['login']); + } + }), + ); + } +} diff --git a/src/app/services/auth/auth.service.spec.ts b/src/app/services/auth/auth.service.spec.ts new file mode 100644 index 000000000..f1251cacf --- /dev/null +++ b/src/app/services/auth/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/auth/auth.service.ts b/src/app/services/auth/auth.service.ts new file mode 100644 index 000000000..29ca282b7 --- /dev/null +++ b/src/app/services/auth/auth.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; +import { + PreferenceManager, +} from 'src/app/utils/preferences/preference-manager'; + +const preference = PreferenceManager.NUMBERS_STORAGE_PUBLISHER_PREF; +const enum PrefKeys { + Enabled = 'enabled', + AuthToken = 'authToken', + UserName = 'userName', + Email = 'email' +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + // The user is authenticated if the user have successfully login once. + // No real authentication API call is made here since the user is allowed to use the App offline + isAuthenticated$(): Observable { + return preference.getString$(PrefKeys.AuthToken) + .pipe( + tap(auth => console.log(auth)), + map(authToken => authToken !== ''), + ); + } +} From 937bff8f329fb5c876dad8f2a736f6ed6e60d2ba Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 08:22:39 +0800 Subject: [PATCH 25/31] Add logout Signed-off-by: James Chien --- src/app/app-routing.module.ts | 4 ++- .../components/header/header.component.html | 6 ++++ src/app/components/header/header.component.ts | 3 ++ .../services/auth/login-guard.service.spec.ts | 16 +++++++++ src/app/services/auth/login-guard.service.ts | 33 +++++++++++++++++++ .../numbers-storage-api.service.ts | 13 ++++++-- 6 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/app/services/auth/login-guard.service.spec.ts create mode 100644 src/app/services/auth/login-guard.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 8c8345dfe..8dfb641ab 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; import { AuthGuardService } from './services/auth/auth-guard.service'; +import { LoginGuardService } from './services/auth/login-guard.service'; const routes: Routes = [{ path: '', @@ -10,7 +11,8 @@ const routes: Routes = [{ }, { path: 'login', - loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule) + loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule), + canActivate: [LoginGuardService], }, { path: 'signup', diff --git a/src/app/components/header/header.component.html b/src/app/components/header/header.component.html index a4462f052..b7485dff6 100644 --- a/src/app/components/header/header.component.html +++ b/src/app/components/header/header.component.html @@ -68,6 +68,12 @@ {{ t('about') }} + + + + {{ t('logout') }} + + \ No newline at end of file diff --git a/src/app/components/header/header.component.ts b/src/app/components/header/header.component.ts index 2b914d68f..dd5e2c49c 100644 --- a/src/app/components/header/header.component.ts +++ b/src/app/components/header/header.component.ts @@ -12,4 +12,7 @@ export class HeaderComponent implements OnInit { ngOnInit() { } + logout() { + console.log('logginng out!');; + } } diff --git a/src/app/services/auth/login-guard.service.spec.ts b/src/app/services/auth/login-guard.service.spec.ts new file mode 100644 index 000000000..01c151c5b --- /dev/null +++ b/src/app/services/auth/login-guard.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoginGuardService } from './login-guard.service'; + +describe('LoginGuardService', () => { + let service: LoginGuardService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(LoginGuardService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/auth/login-guard.service.ts b/src/app/services/auth/login-guard.service.ts new file mode 100644 index 000000000..2b8b85fc8 --- /dev/null +++ b/src/app/services/auth/login-guard.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, +} from '@angular/router'; + +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; + +import { + NumbersStorageApi, +} from '../publisher/numbers-storage/numbers-storage-api.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LoginGuardService implements CanActivate { + + constructor( + private readonly numbersStorageApi: NumbersStorageApi, + ) { } + + canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + if ('true' === next.queryParamMap.get('logout')) { + return this.numbersStorageApi.logout$() + .pipe( + catchError(() => of(false)), + map(logout => logout !== false), + ); + } else { + return of(true); + } + } +} diff --git a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts index 998a479f1..4c950f706 100644 --- a/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts +++ b/src/app/services/publisher/numbers-storage/numbers-storage-api.service.ts @@ -1,12 +1,18 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; + import { of, zip } from 'rxjs'; import { concatMap, concatMapTo, first, map, pluck } from 'rxjs/operators'; import { base64ToBlob$ } from 'src/app/utils/encoding/encoding'; -import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; +import { + PreferenceManager, +} from 'src/app/utils/preferences/preference-manager'; + import { Proof } from '../../data/proof/proof'; import { Signature } from '../../data/signature/signature'; -import { SerializationService } from '../../serialization/serialization.service'; +import { + SerializationService, +} from '../../serialization/serialization.service'; import { baseUrl } from './secret'; export const enum TargetProvider { @@ -87,7 +93,8 @@ export class NumbersStorageApi { concatMapTo(preference.getString$(PrefKeys.AuthToken)), first(), map(authToken => new HttpHeaders({ Authorization: authToken })), - concatMap(headers => this.httpClient.post(`${baseUrl}/auth/token/logout/`, new FormData(), { headers })) + concatMap(headers => this.httpClient.post(`${baseUrl}/auth/token/logout/`, new FormData(), { headers })), + concatMap(() => preference.setString$(PrefKeys.AuthToken, '')), ); } From 1d3c38252d3d73e8f11279aa4f466a72fc3a1397 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 08:24:30 +0800 Subject: [PATCH 26/31] Remove duplicated i18n keys Signed-off-by: James Chien --- src/assets/i18n/en-us.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 2210ff3f9..6c9d82a4b 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -52,10 +52,6 @@ "collectLocationInfo": "Collect Location Info", "publisher": "Publisher", "userName": "User Name", - "email": "Email", - "password": "Password", - "login": "Login", - "logout": "Logout", "signUp": "Sign Up", "required": "Required", "tooShort": "Too Short", From 02cc382106b2c149400f30ec0283687017cad3b7 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 08:34:37 +0800 Subject: [PATCH 27/31] Remove unused function Signed-off-by: James Chien --- src/app/components/header/header.component.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/app/components/header/header.component.ts b/src/app/components/header/header.component.ts index dd5e2c49c..2b914d68f 100644 --- a/src/app/components/header/header.component.ts +++ b/src/app/components/header/header.component.ts @@ -12,7 +12,4 @@ export class HeaderComponent implements OnInit { ngOnInit() { } - logout() { - console.log('logginng out!');; - } } From 1d019fe287ce8eb81a5d1672cb4349274cec4747 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 08:49:31 +0800 Subject: [PATCH 28/31] Fix zh-tw i18n typo Signed-off-by: James Chien --- src/assets/i18n/zh-tw.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index 87b1862c9..40c80f777 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -77,7 +77,7 @@ "pleaseCheckYourEmail": "請檢查您的電子郵件收件箱,然後按照電子郵件中的說明啟用您的帳戶。", "emailAlreadyExists": "此電子郵件帳戶已經存在。", "pleaseEnterValidEmail": "請輸入有效的電子郵件地址。", - "passwordMustBeBetween": "密碼長度必須介於{{min}}至{{max}個字元之間。", + "passwordMustBeBetween": "密碼長度必須介於{{min}}至{{max}}個字元之間。", "passwordNotMatching": "密碼不符。", "accountNotActivated": "此帳戶尚未啟用。", "ifYouNotReceiveEmail": "如果您在10分鐘內仍未收到驗證電子郵件,請單擊下面的按鈕重新發送驗證電子郵件。", From 6b11d0df2ce3151a7dc3ac819ac3235cffea21c6 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 09:09:00 +0800 Subject: [PATCH 29/31] Remove unused code & fix signup message Signed-off-by: James Chien --- src/app/pages/login/login.page.ts | 4 +--- src/app/pages/signup/signup.page.ts | 20 ++++++++++---------- src/app/services/auth/auth.service.ts | 3 +-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/app/pages/login/login.page.ts b/src/app/pages/login/login.page.ts index 852c02b23..9e540e786 100644 --- a/src/app/pages/login/login.page.ts +++ b/src/app/pages/login/login.page.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; -import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { combineLatest } from 'rxjs'; import { BlockingActionService, } from 'src/app/services/blocking-action/blocking-action.service'; @@ -31,8 +31,6 @@ export class LoginPage implements OnInit { options: FormlyFormOptions = {}; fields: FormlyFieldConfig[] = []; formInitialized = false; - private readonly messageSubject$ = new BehaviorSubject(''); - message$: Observable = this.messageSubject$; constructor( private readonly blockingActionService: BlockingActionService, diff --git a/src/app/pages/signup/signup.page.ts b/src/app/pages/signup/signup.page.ts index dfcfffe38..7d802ad2e 100644 --- a/src/app/pages/signup/signup.page.ts +++ b/src/app/pages/signup/signup.page.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { combineLatest } from 'rxjs'; import { BlockingActionService, } from 'src/app/services/blocking-action/blocking-action.service'; @@ -34,12 +34,10 @@ interface MessageConfig { export class SignupPage implements OnInit { form = new FormGroup({}); - model = { email: '', password: '', repeatPassword: '' }; + model: LoginFormModel = { email: '', password: '', repeatPassword: '' }; options: FormlyFormOptions = {}; fields: FormlyFieldConfig[] = []; formInitialized = false; - private readonly messageConfigSubject$ = new BehaviorSubject({ color: '', text: '' }); - messageConfig$: Observable = this.messageConfigSubject$; constructor( private readonly blockingActionService: BlockingActionService, @@ -74,12 +72,14 @@ export class SignupPage implements OnInit { untilDestroyed(this), ).subscribe( () => { - this.messageConfigSubject$.next({ - color: 'primary', - text: - this.translocoService.translate('message.verificationEmailSent') + - this.translocoService.translate('message.pleaseCheckYourEmail') - }); + this.toastController + .create({ + message: this.translocoService.translate('message.verificationEmailSent') + + this.translocoService.translate('message.pleaseCheckYourEmail'), + duration: 8000, + color: 'primary', + }) + .then(toast => toast.present()); if (this?.options?.resetModel) { this.options.resetModel(); } diff --git a/src/app/services/auth/auth.service.ts b/src/app/services/auth/auth.service.ts index 29ca282b7..31b842693 100644 --- a/src/app/services/auth/auth.service.ts +++ b/src/app/services/auth/auth.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { PreferenceManager, } from 'src/app/utils/preferences/preference-manager'; @@ -24,7 +24,6 @@ export class AuthService { isAuthenticated$(): Observable { return preference.getString$(PrefKeys.AuthToken) .pipe( - tap(auth => console.log(auth)), map(authToken => authToken !== ''), ); } From 5cd25b0cfe3a517a87bfcbcafaf5ef55a75adbd7 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 10:19:19 +0800 Subject: [PATCH 30/31] Fix test providers Signed-off-by: James Chien --- src/app/app.module.ts | 3 ++- src/app/pages/login/login.page.spec.ts | 23 +++++++++++++++++-- src/app/pages/login/login.page.ts | 4 ++-- src/app/pages/signup/signup.page.spec.ts | 23 +++++++++++++++++-- src/app/pages/signup/signup.page.ts | 8 +++---- .../services/auth/auth-guard.service.spec.ts | 16 ++++++++++++- .../services/auth/login-guard.service.spec.ts | 16 ++++++++++++- 7 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 87e5ee266..5f3eccf67 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,7 +2,7 @@ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; -import { RouteReuseStrategy } from '@angular/router'; +import { RouteReuseStrategy, RouterModule } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { FormlyModule } from '@ngx-formly/core'; @@ -25,6 +25,7 @@ import { TranslocoRootModule } from './transloco/transloco-root.module'; ReactiveFormsModule, FormlyModule.forRoot({ extras: { lazyRender: true } }), FormlyIonicModule, + RouterModule.forRoot([]) ], providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], bootstrap: [AppComponent] diff --git a/src/app/pages/login/login.page.spec.ts b/src/app/pages/login/login.page.spec.ts index 07cb76a93..49dab8c6e 100644 --- a/src/app/pages/login/login.page.spec.ts +++ b/src/app/pages/login/login.page.spec.ts @@ -1,4 +1,14 @@ +import { HttpClient } from '@angular/common/http'; +import { + HttpClientTestingModule, HttpTestingController, +} from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { + getTranslocoModule, +} from 'src/app/transloco/transloco-root.module.spec'; + import { IonicModule } from '@ionic/angular'; import { LoginPage } from './login.page'; @@ -6,16 +16,25 @@ import { LoginPage } from './login.page'; describe('LoginPage', () => { let component: LoginPage; let fixture: ComponentFixture; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ LoginPage ], - imports: [IonicModule.forRoot()] + declarations: [LoginPage], + imports: [ + IonicModule.forRoot(), + HttpClientTestingModule, + getTranslocoModule(), + RouterTestingModule, + ] }).compileComponents(); fixture = TestBed.createComponent(LoginPage); component = fixture.componentInstance; fixture.detectChanges(); + httpClient = TestBed.inject(HttpClient); + httpTestingController = TestBed.inject(HttpTestingController); })); it('should create', () => { diff --git a/src/app/pages/login/login.page.ts b/src/app/pages/login/login.page.ts index 9e540e786..f90eaaf9a 100644 --- a/src/app/pages/login/login.page.ts +++ b/src/app/pages/login/login.page.ts @@ -54,8 +54,8 @@ export class LoginPage implements OnInit { ); } - onSubmit() { - const action$ = this.numbersStorageApi.login$(this.model.email, this.model.password); + onSubmit(model: LoginFormModel) { + const action$ = this.numbersStorageApi.login$(model.email, model.password); this.blockingActionService.run$( action$, { message: this.translocoService.translate('message.pleaseWait') } diff --git a/src/app/pages/signup/signup.page.spec.ts b/src/app/pages/signup/signup.page.spec.ts index 1b0c3b114..92b07508e 100644 --- a/src/app/pages/signup/signup.page.spec.ts +++ b/src/app/pages/signup/signup.page.spec.ts @@ -1,4 +1,14 @@ +import { HttpClient } from '@angular/common/http'; +import { + HttpClientTestingModule, HttpTestingController, +} from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { + getTranslocoModule, +} from 'src/app/transloco/transloco-root.module.spec'; + import { IonicModule } from '@ionic/angular'; import { SignupPage } from './signup.page'; @@ -6,16 +16,25 @@ import { SignupPage } from './signup.page'; describe('SignupPage', () => { let component: SignupPage; let fixture: ComponentFixture; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ SignupPage ], - imports: [IonicModule.forRoot()] + declarations: [SignupPage], + imports: [ + IonicModule.forRoot(), + HttpClientTestingModule, + getTranslocoModule(), + RouterTestingModule + ] }).compileComponents(); fixture = TestBed.createComponent(SignupPage); component = fixture.componentInstance; fixture.detectChanges(); + httpClient = TestBed.inject(HttpClient); + httpTestingController = TestBed.inject(HttpTestingController); })); it('should create', () => { diff --git a/src/app/pages/signup/signup.page.ts b/src/app/pages/signup/signup.page.ts index 7d802ad2e..b6a607031 100644 --- a/src/app/pages/signup/signup.page.ts +++ b/src/app/pages/signup/signup.page.ts @@ -14,7 +14,7 @@ import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core'; -interface LoginFormModel { +interface SignupFormModel { email: string; password: string; repeatPassword: string; @@ -34,7 +34,7 @@ interface MessageConfig { export class SignupPage implements OnInit { form = new FormGroup({}); - model: LoginFormModel = { email: '', password: '', repeatPassword: '' }; + model: SignupFormModel = { email: '', password: '', repeatPassword: '' }; options: FormlyFormOptions = {}; fields: FormlyFieldConfig[] = []; formInitialized = false; @@ -61,9 +61,9 @@ export class SignupPage implements OnInit { ); } - onSubmit() { + onSubmit(model: SignupFormModel) { const defaultUsername = ''; - const action$ = this.numbersStorageApi.createUser$(defaultUsername, this.model.email, this.model.password); + const action$ = this.numbersStorageApi.createUser$(defaultUsername, model.email, model.password); this.blockingActionService.run$( action$, diff --git a/src/app/services/auth/auth-guard.service.spec.ts b/src/app/services/auth/auth-guard.service.spec.ts index 35afd3775..6c1c1263b 100644 --- a/src/app/services/auth/auth-guard.service.spec.ts +++ b/src/app/services/auth/auth-guard.service.spec.ts @@ -1,13 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { + HttpClientTestingModule, HttpTestingController, +} from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { AuthGuardService } from './auth-guard.service'; describe('AuthGuardService', () => { let service: AuthGuardService; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + RouterTestingModule, + ], + }); service = TestBed.inject(AuthGuardService); + httpClient = TestBed.inject(HttpClient); + httpTestingController = TestBed.inject(HttpTestingController); }); it('should be created', () => { diff --git a/src/app/services/auth/login-guard.service.spec.ts b/src/app/services/auth/login-guard.service.spec.ts index 01c151c5b..7c71935f0 100644 --- a/src/app/services/auth/login-guard.service.spec.ts +++ b/src/app/services/auth/login-guard.service.spec.ts @@ -1,13 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { + HttpClientTestingModule, HttpTestingController, +} from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { LoginGuardService } from './login-guard.service'; describe('LoginGuardService', () => { let service: LoginGuardService; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + RouterTestingModule, + ], + }); service = TestBed.inject(LoginGuardService); + httpClient = TestBed.inject(HttpClient); + httpTestingController = TestBed.inject(HttpTestingController); }); it('should be created', () => { From 2d12902965afa73809b13b8844e91cf810a3ad3c Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 13 Oct 2020 10:40:00 +0800 Subject: [PATCH 31/31] Remove "Bring Trust Into Data" Signed-off-by: James Chien --- src/app/pages/login/login.page.html | 5 ----- src/app/pages/signup/signup.page.html | 5 ----- 2 files changed, 10 deletions(-) diff --git a/src/app/pages/login/login.page.html b/src/app/pages/login/login.page.html index 7eba3d5f3..d3916efdd 100644 --- a/src/app/pages/login/login.page.html +++ b/src/app/pages/login/login.page.html @@ -11,11 +11,6 @@ - - - {{ t('bringTrustIntoData') }} - - diff --git a/src/app/pages/signup/signup.page.html b/src/app/pages/signup/signup.page.html index 46191c1af..7e7be6f10 100644 --- a/src/app/pages/signup/signup.page.html +++ b/src/app/pages/signup/signup.page.html @@ -11,11 +11,6 @@ - - - {{ t('bringTrustIntoData') }} - -