New users can now be created.

- Note: Login is buggy with a brand new account
This commit is contained in:
2020-10-31 01:00:01 +01:00
parent b3b3d9d9c4
commit d747b7be5b
14 changed files with 284 additions and 39 deletions

View File

@@ -1 +1,14 @@
<p>admin works!</p>
<h2>Create new user</h2>
<form (ngSubmit)="createUser()" #form="ngForm">
<input name="username" [(ngModel)]="newUsername" placeholder="Username"><br>
<input name="password" [(ngModel)]="newPassword" type="password" placeholder="Password"><br>
<select name="type" [(ngModel)]="newType">
<option value="guest">Guest</option>
<option value="user">User</option>
<option value="admin">Admin</option>
</select><br>
<button class="btn btn-success" type="submit">Create user</button>
<p *ngIf="this.invitationCode !== undefined">Send the following URL to the new owner of this account in order to setup the new account:
<a [href]="'http://localhost:4200/login&setup=' + invitationCode">http://localhost:4200/login&setup={{invitationCode}}</a>
</p>
</form>

View File

@@ -1,5 +1,5 @@
import { AfterContentInit, Component, OnDestroy, OnInit } from '@angular/core';
import { APIService } from '../api.service';
import { APIService, UserType } from '../api.service';
@Component({
selector: 'app-admin',
@@ -8,6 +8,12 @@ import { APIService } from '../api.service';
})
export class AdminComponent implements AfterContentInit, OnDestroy {
// New user form
newUsername = '';
newPassword = '';
newType: UserType;
invitationCode: string;
constructor(public api: APIService) { }
ngAfterContentInit(): void {
@@ -19,4 +25,8 @@ export class AdminComponent implements AfterContentInit, OnDestroy {
this.api.showFilter = true;
}
async createUser(): Promise<void> {
this.invitationCode = await this.api.createUser(this.newUsername, this.newPassword, this.newType);
}
}

View File

@@ -1,8 +1,9 @@
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MqttService } from 'ngx-mqtt';
import { BehaviorSubject } from 'rxjs';
import * as moment from 'moment';
import { error } from 'protractor';
export interface ILogin {
token: string;
@@ -37,6 +38,7 @@ export enum UserType {
}
export interface IUser {
_id: string;
name: string;
brokerToken: string;
type: UserType;
@@ -46,6 +48,7 @@ export interface IUser {
}
export interface IPhone {
_id: string;
androidId: string;
displayName: string;
modelName: string;
@@ -87,6 +90,7 @@ export class APIService {
};
phones: IPhone[];
user: IUser = {
_id: '',
name: '',
brokerToken: '',
lastLogin: new Date(2020, 3, 1),
@@ -126,6 +130,9 @@ export class APIService {
});
}
/*
USER
*/
async login(username: string, password: string): Promise<ILogin> {
return new Promise<ILogin>(async (resolve, reject) => {
this.httpClient.post(this.API_ENDPOINT + '/user/login', { username, password }, { responseType: 'json' })
@@ -147,6 +154,36 @@ export class APIService {
});
}
/**
* This function will create a new user.
* @returns Invitation code that must be send to the new accounts user.
*/
async createUser(name: string, password: string, type: UserType): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (this.user.type !== UserType.ADMIN) {
reject('This function can only be executed by admins! You\'re not an admin.');
return;
}
if (this.token === undefined) { reject('Login is required to execute this function.'); }
const headers = new HttpHeaders({ token: this.token });
this.fetchingDataEvent.next(true);
this.httpClient.post(this.API_ENDPOINT + '/user', { name, password, type }, { responseType: 'json', headers })
.subscribe((res: any) => {
this.fetchingDataEvent.next(false);
console.log('Response:', res);
resolve(res.setupToken);
});
});
}
/*
BEATS
*/
async getBeats(): Promise<IBeat[]> {
return new Promise<IBeat[]>((resolve, reject) => {
if (this.token === undefined) { reject([]); }
@@ -174,7 +211,6 @@ export class APIService {
});
});
}
async getBeatStats(): Promise<IBeatStats> {
return new Promise<IBeatStats>((resolve, reject) => {
if (this.token === undefined) { reject([]); }
@@ -192,19 +228,46 @@ export class APIService {
});
}
async getUserInfo(): Promise<IUser> {
return new Promise<IUser>((resolve, reject) => {
/**
* Fetch last beat of provided phone.
* @param forPhone Object id of wanted phone (not android id)
*/
async getFetchLastBeat(forPhone: string): Promise<IBeat> {
return new Promise<IBeat>((resolve, reject) => {
if (this.token === undefined) { reject([]); }
const headers = new HttpHeaders({ token: this.token });
let params = new HttpParams()
.set('limit', '1')
.set('sort', '-1');
if (forPhone !== undefined) {
params = params.set('phoneId', forPhone);
}
this.httpClient.get(this.API_ENDPOINT + '/beat', { responseType: 'json', headers, params })
.subscribe(beat => {
resolve(beat[0] as IBeat);
});
});
}
async getUserInfo(userId: string = ''): Promise<IUser | null> {
return new Promise<IUser | null>((resolve, reject) => {
if (this.token === undefined) { reject([]); }
const headers = new HttpHeaders({ token: this.token });
this.fetchingDataEvent.next(true);
this.httpClient.get(this.API_ENDPOINT + '/user', { responseType: 'json', headers })
this.httpClient.get(this.API_ENDPOINT + '/user/' + userId, { responseType: 'json', headers })
.subscribe(user => {
this.user = user as IUser;
this.fetchingDataEvent.next(false);
resolve(user as IUser);
}, () => {
this.fetchingDataEvent.next(false);
resolve(null);
});
});
}
@@ -271,7 +334,7 @@ export class APIService {
/**
* Short form for `this.api.beats[this.api.beats.length - 1]`
*
* **Notice:** This does not fetch new beats, instead use cached `this.beats`
* **Notice:** This does not fetch new beats, instead uses cached `this.beats`. Use `getFetchLastBeat()` instead.
*/
getLastBeat(): IBeat {
return this.beats[this.beats.length - 1];

View File

@@ -25,7 +25,7 @@ const routes: Routes = [
component: MapComponent
},
{
path: 'user',
path: 'user/:id',
component: UserComponent
},
{

View File

@@ -2,7 +2,7 @@
<ul class="navbar">
<li><a [routerLink]="['/dashboard']" routerLinkActive="router-link-active" >Dashboard</a></li>
<li><a [routerLink]="['/map']" routerLinkActive="router-link-active" >Map</a></li>
<li class="navbar-right"><a [routerLink]="['/user']" routerLinkActive="router-link-active" >{{this.api.username}}</a></li>
<li class="navbar-right"><a [routerLink]="['/user', this.api.user._id]" routerLinkActive="router-link-active" >{{this.api.username}}</a></li>
<li class="navbar-right"><a [routerLink]="['/admin']" routerLinkActive="router-link-active" *ngIf="this.api.user.type == 'admin'">Admin settings</a></li>
</ul>
</div>

View File

@@ -75,6 +75,7 @@ export class DashboardComponent implements AfterViewInit {
for (let i = 0; i < beats.length; i++) {
if (i >= beats.length || (i + 1) >= beats.length) { break; }
if (beats[i].accuracy > this.api.maxAccuracy.value) { continue; }
const dist1 = beats[i].coordinate;
const dist2 = beats[i + 1].coordinate;
tDistance += this.api.distanceInKmBetweenEarthCoordinates(dist1[0], dist1[1], dist2[0], dist2[1]);

View File

@@ -1,14 +1,18 @@
<div id="user">
<h1>{{this.api.user.name}}
<span class="adminState" *ngIf="this.api.user.type == 'admin'">Admin &#2B50;</span>
<div id="user" *ngIf="show">
<h1>{{this.userInfo.name}}
<span class="adminState" *ngIf="this.userInfo.type == 'admin'">Admin &#2B50;</span>
</h1>
<p class="lastLogin">Last login was {{lastLogin}}</p><br>
<h2>Devices</h2>
<ul class="phoneListing">
<h2 *ngIf="showDevices">Devices</h2>
<ul class="phoneListing" *ngIf="showDevices">
<li *ngFor="let phone of this.api.phones">
<h2>{{phone.displayName}}</h2>
<h2>{{phone.displayName}} <span class="lastBeat">last beat was {{ this.lastBeats.get(phone._id) }}</span></h2>
<p>{{phone.modelName}}</p>
</li>
</ul>
</div>
<div *ngIf="userNotFound" id="userNotFound">
<h1>This user doesn't exist!</h1>
<p>This user may have never existed</p>
</div>

View File

@@ -7,6 +7,10 @@
margin-right: 20rem;
}
#userNotFound {
text-align: center;
}
.adminState {
padding-left: 1rem;
font-weight: bolder;
@@ -19,4 +23,9 @@
padding: 1.5rem;
border-radius: 10px;
background-color: $darker;
}
.lastBeat {
font-weight: lighter;
font-size: 10pt;
}

View File

@@ -1,6 +1,7 @@
import { AfterContentInit, Component, OnDestroy, OnInit } from '@angular/core';
import { APIService } from '../api.service';
import { APIService, IBeat, IUser } from '../api.service';
import * as moment from 'moment';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user',
@@ -10,15 +11,51 @@ import * as moment from 'moment';
export class UserComponent implements AfterContentInit, OnDestroy {
lastLogin: string;
lastBeats: Map<string, string> = new Map<string, string>();
show = false;
showDevices = false;
userNotFound = false;
constructor(public api: APIService) {
// User data of selected user.
userInfo: IUser;
constructor(public api: APIService, private route: ActivatedRoute) {
this.api.loginEvent.subscribe(status => {
if (status) {
this.lastLogin = moment(this.api.user.lastLogin).fromNow();
console.log(this.lastLogin);
this.route.params.subscribe(async params => {
// Check if selected user is the current logged in user.
if (params.id === this.api.user._id) {
this.userInfo = this.api.user;
this.userNotFound = false;
this.showDevices = true;
} else {
this.userInfo = await this.api.getUserInfo(params.id);
if (this.userInfo === null) {
this.userNotFound = true;
} else {
this.userNotFound = false;
}
// Don't show devices if it's not us since we don't have the permissions.
this.showDevices = false;
}
this.show = true;
});
}
});
this.api.phoneEvent.subscribe(async phones => {
// Get last beats for all phone
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < phones.length; i++) {
const phone = phones[i];
const beat = await this.api.getFetchLastBeat(phone._id);
this.lastBeats.set(phone._id, moment(beat.createdAt).fromNow());
}
this.showDevices = true;
});
}
ngAfterContentInit(): void {