diff --git a/backend/app.ts b/backend/app.ts index 4bbde4c..9d13c64 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -149,7 +149,8 @@ async function run() { app.delete('/user/:id', MW_User, (req, res) => DeleteUser(req, res)); app.post('/user/login', (req, res) => LoginUser(req, res)); - app.get('/phone/:id', (req, res) => GetPhone(req, res)); + app.get('/phone', MW_User, (req, res) => GetPhone(req, res)); + app.get('/phone/:id', MW_User, (req, res) => GetPhone(req, res)); app.post('/phone', MW_User, (req, res) => PostPhone(req, res)); app.get('/beat/', MW_User, (req, res) => GetBeat(req, res)); diff --git a/backend/endpoints/phone.ts b/backend/endpoints/phone.ts index 58f7d88..2075555 100644 --- a/backend/endpoints/phone.ts +++ b/backend/endpoints/phone.ts @@ -1,13 +1,16 @@ import { Response } from "express"; import { logger } from "../app"; import { LivebeatRequest } from "../lib/request"; +import { Beat } from "../models/beat/beat.model."; import { Phone } from "../models/phone/phone.model"; export async function GetPhone(req: LivebeatRequest, res: Response) { const phoneId: String = req.params['id']; + // If none id provided, return all. if (phoneId === undefined) { - res.status(400).send(); + const phone = await Phone.find({ user: req.user?._id }); + res.status(200).send(phone); return; } @@ -18,7 +21,10 @@ export async function GetPhone(req: LivebeatRequest, res: Response) { return; } - res.status(200).send(phone); + // Get last beat + const lastBeat = await Beat.findOne({ phone: phone?._id }).sort({ createdAt: -1 }); + + res.status(200).send({ phone, lastBeat }); } export async function PostPhone(req: LivebeatRequest, res: Response) { diff --git a/backend/endpoints/user.ts b/backend/endpoints/user.ts index e14a7c3..1d93b21 100644 --- a/backend/endpoints/user.ts +++ b/backend/endpoints/user.ts @@ -5,9 +5,15 @@ import { sign, decode, verify } from 'jsonwebtoken'; import { JWT_SECRET, logger } from "../app"; import { LivebeatRequest } from '../lib/request'; import { SchemaTypes } from "mongoose"; +import { Phone } from "../models/phone/phone.model"; -export async function GetUser(req: Request, res: Response) { - +export async function GetUser(req: LivebeatRequest, res: Response) { + let user: any = req.user; + user.password = undefined; + user.salt = undefined; + user.__v = undefined; + + res.status(200).send(user); } export async function DeleteUser(req: Request, res: Response) { @@ -51,6 +57,9 @@ export async function LoginUser(req: Request, res: Response) { // We're good. Create JWT token. const token = sign({ user: user._id }, JWT_SECRET, { expiresIn: '30d' }); + user.lastLogin = new Date(Date.now()); + await user.save(); + logger.info(`User ${user.name} logged in.`) res.status(200).send({ token }); } diff --git a/backend/models/beat/beat.schema.ts b/backend/models/beat/beat.schema.ts index 66367cb..43311a7 100644 --- a/backend/models/beat/beat.schema.ts +++ b/backend/models/beat/beat.schema.ts @@ -11,7 +11,8 @@ const schemaBeat = new Schema({ }, { timestamps: { createdAt: true - } + }, + versionKey: false }); export { schemaBeat }; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 94470cd..5716cf9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1979,6 +1979,14 @@ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=" }, + "@types/moment": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", + "integrity": "sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=", + "requires": { + "moment": "*" + } + }, "@types/node": { "version": "12.12.67", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.67.tgz", diff --git a/frontend/package.json b/frontend/package.json index fc8bc2d..ebe5f2c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,10 +22,12 @@ "@angular/router": "~10.1.5", "@types/chart.js": "^2.9.27", "@types/mapbox-gl": "^1.12.5", + "@types/moment": "^2.13.0", "chart.js": "^2.9.4", "eva-icons": "^1.1.3", "geojson": "^0.5.0", "mapbox-gl": "^1.12.0", + "moment": "^2.29.1", "ng2-charts": "^2.4.2", "ngx-mapbox-gl": "^4.8.1", "rxjs": "~6.6.0", diff --git a/frontend/src/app/api.service.ts b/frontend/src/app/api.service.ts index 0f94211..9e29b9f 100644 --- a/frontend/src/app/api.service.ts +++ b/frontend/src/app/api.service.ts @@ -15,6 +15,33 @@ export interface IBeat { createdAt?: Date; } +export enum UserType { + ADMIN = 'admin', + USER = 'user', + GUEST = 'guest' +} + +export interface IUser { + name: string; + password: string; + salt: string; + type: UserType; + lastLogin: Date; + twoFASecret?: string; + createdAt?: Date; +} + +export interface IPhone { + androidId: string; + displayName: string; + modelName: string; + operatingSystem: string; + architecture: string; + user: IUser; + updatedAt?: Date; + createdAt?: Date; +} + export interface ITimespan { from?: number; to?: number; @@ -29,9 +56,20 @@ export class APIService { username: string; time: ITimespan | undefined; + showFilter = true; // Cached data beats: IBeat[]; + phones: IPhone[]; + user: IUser = { + name: '', + lastLogin: new Date(2020, 3, 1), + password: '', + salt: '', + type: UserType.GUEST, + createdAt: new Date(), + twoFASecret: '' + }; // Events when new data got fetched beatsEvent: BehaviorSubject = new BehaviorSubject([]); @@ -43,15 +81,15 @@ export class APIService { constructor(private httpClient: HttpClient) { } async login(username: string, password: string): Promise { - return new Promise((resolve, reject) => { - console.log('POST'); - + return new Promise(async (resolve, reject) => { this.httpClient.post(this.API_ENDPOINT + '/user/login', { username, password }, { responseType: 'json' }) - .subscribe(token => { + .subscribe(async token => { console.log(token); this.token = (token as ILogin).token; this.username = username; + await this.getPhones(); + await this.getUserInfo(); this.loginEvent.next(true); resolve(token as ILogin); }); @@ -73,8 +111,6 @@ export class APIService { params = params.set('to', this.time.to.toString()); } - console.log(params); - this.httpClient.get(this.API_ENDPOINT + '/beat', { responseType: 'json', headers, params }) .subscribe(beats => { this.beats = beats as IBeat[]; @@ -85,6 +121,56 @@ export class APIService { }); } + async getUserInfo(): Promise { + return new Promise((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 }) + .subscribe(user => { + this.user = user as IUser; + this.fetchingDataEvent.next(false); + resolve(user as IUser); + }); + }); + } + + async getPhones(): Promise { + return new Promise((resolve, reject) => { + if (this.token === undefined) { reject([]); } + + const headers = new HttpHeaders({ token: this.token }); + + this.fetchingDataEvent.next(true); + + this.httpClient.get(this.API_ENDPOINT + '/phone', { responseType: 'json', headers }) + .subscribe(phones => { + this.phones = phones as IPhone[]; + this.fetchingDataEvent.next(false); + resolve(phones as IPhone[]); + }); + }); + } + + async getPhone(phoneId: string): Promise<{ IPhone, IBeat }> { + return new Promise<{ IPhone, IBeat }>((resolve, reject) => { + if (this.token === undefined) { reject([]); } + + const headers = new HttpHeaders({ token: this.token }); + + this.fetchingDataEvent.next(true); + + this.httpClient.get(this.API_ENDPOINT + '/phone/' + phoneId, { responseType: 'json', headers }) + .subscribe(phones => { + this.fetchingDataEvent.next(false); + resolve(phones as {IPhone, IBeat}); + }); + }); + } + hasSession(): boolean { return this.token !== undefined; } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index afe158a..1502e34 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -4,6 +4,7 @@ import { AppComponent } from './app.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { LoginComponent } from './login/login.component'; import { MapComponent } from './map/map.component'; +import { UserComponent } from './user/user.component'; const routes: Routes = [ { @@ -21,6 +22,10 @@ const routes: Routes = [ { path: 'map', component: MapComponent + }, + { + path: 'user', + component: UserComponent } ]; diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index b947b47..5d4fb64 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,13 +1,14 @@ -