Join and leave travels

This commit is contained in:
Eneko Nieto
2021-02-20 01:53:08 +01:00
parent 77497a642d
commit d36457e0ea
8 changed files with 174 additions and 40 deletions

View File

@@ -0,0 +1,17 @@
import { User, UserId } from './user';
export type TravelId = number;
export class TravelDto {
constructor(
public id: TravelId = -1,
public driverInfo: User = null,
public travelersInfo: User[] = [],
public departureDate: string = '',
public origin: string = '',
public destination: string = '',
public places: number = 0,
public matrixRoomId: string = '',
public description?: string
) { }
}

View File

@@ -1,21 +1,32 @@
import { User } from './user';
import { TravelDto } from './travel-dto';
import { User, UserId } from './user';
export type TravelId = number;
export class Travel {
constructor(
public id: TravelId = -1,
public driverInfo: User = null,
public travelersInfo: User[] = [],
public departureDate: string = '',
public origin: string = '',
public destination: string = '',
public places: number = 0,
public matrixRoomId: string = '',
public description?: string
) { }
export class Travel extends TravelDto {
constructor(travelDto: TravelDto) {
super(travelDto.id,
travelDto.driverInfo,
travelDto.travelersInfo,
travelDto.departureDate,
travelDto.origin,
travelDto.destination,
travelDto.places,
travelDto.matrixRoomId,
travelDto.description);
}
availablePlaces(): number {
return this.places - this.travelersInfo.length;
}
hasTraveler(id: UserId): boolean {
let hasTraveler = false;
this.travelersInfo.forEach(traveler => {
if (traveler.id === id) {
hasTraveler = true;
}
});
return hasTraveler;
}
}

View File

@@ -9,8 +9,14 @@
<p>ID: {{ travel.id }}</p>
<p>Conductor: {{ travel.driverInfo.name }}</p><br>
<p>{{ travel.description }}</p>
<p>Travelers</p>
<ul>
<li *ngFor="let traveler of travel.travelersInfo">{{ traveler.name }}</li>
</ul>
</mat-card-content>
</mat-card>
<button mat-raised-button class="btn btn-default" (click)="goBack()">Back</button>
<button *ngIf="canEdit" mat-raised-button class="btn btn-default" (click)="edit()">Edit</button>
<button *ngIf="canJoin" mat-raised-button class="btn btn-default" (click)="join()">Join</button>
<button *ngIf="canLeave" mat-raised-button class="btn btn-default" (click)="leave()">Leave</button>
<button *ngIf="canEdit" mat-raised-button class="btn btn-default" (click)="edit()">Edit</button>

View File

@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';
import { AbilityService } from 'src/app/services/ability.service';
import { Travel, TravelId } from '../../entities/travel';
import { ApiService } from '../../services/api.service';
@@ -16,7 +16,7 @@ export class TravelComponent implements OnInit {
constructor(
private router: Router,
private authService: AuthService,
private abilityService: AbilityService,
private apiService: ApiService,
private route: ActivatedRoute
) { }
@@ -25,15 +25,7 @@ export class TravelComponent implements OnInit {
this.route.queryParams.subscribe(params => {
this.travelId = this.route.snapshot.paramMap.get('id') as unknown as TravelId;
this.apiService.getTravel(this.travelId)
.subscribe(res => {
if (res.success) {
this.travel = res.data;
}
else {
console.error('Error getting travel ' + this.travelId + ': ' + res.error);
}
});
this.loadTravel();
});
}
@@ -41,11 +33,57 @@ export class TravelComponent implements OnInit {
this.router.navigateByUrl('/');
}
get canJoin(): boolean {
return this.abilityService.canJoinTravel(this.travel);
}
join(): void {
this.apiService.joinTravel(this.travel.id)
.subscribe(res => {
if (res.success) {
console.log('Joined travel');
this.loadTravel();
}
else {
console.error('Error joining travel id=' + this.travel.id + ': ' + res.error.code + ' ' + res.error.msg);
}
});
}
get canLeave(): boolean {
return this.abilityService.canLeaveTravel(this.travel);
}
leave(): void {
this.apiService.leaveTravel(this.travel.id)
.subscribe(res => {
if (res.success) {
console.log('Left travel');
this.loadTravel();
}
else {
console.error('Error leaving travel id=' + this.travel.id + ': ' + res.error.code + ' ' + res.error.msg);
}
});
}
get canEdit(): boolean {
return this.abilityService.canEditTravel(this.travel);
}
edit(): void {
this.router.navigateByUrl(`/travel/${this.travelId}/edit`);
}
get canEdit(): boolean {
return this.authService.canEditTravel(this.travel);
private loadTravel(): void {
this.apiService.getTravel(this.travelId)
.subscribe(res => {
if (res.success) {
this.travel = new Travel(res.data);
}
else {
console.error('Error getting travel ' + this.travelId + ': ' + res.error);
}
});
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AbilityService } from './ability.service';
describe('AbilityService', () => {
let service: AbilityService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AbilityService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { Travel } from '../entities/travel';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AbilityService {
constructor(
private authService: AuthService
) { }
canEditTravel(travel: Travel): boolean {
if (travel == null) {
return false;
}
const user = this.authService.currentUser();
if (user == null) {
return false;
}
return user.admin || (user.id === travel.driverInfo.id);
}
canJoinTravel(travel: Travel): boolean {
if (travel == null) {
return false;
}
const user = this.authService.currentUser();
if (user == null) {
return false;
}
return (travel.driverInfo.id !== user.id) && (! travel.hasTraveler(user.id));
}
canLeaveTravel(travel: Travel): boolean {
if (travel == null) {
return false;
}
const user = this.authService.currentUser();
if (user == null) {
return false;
}
return travel.hasTraveler(user.id);
}
}

View File

@@ -8,6 +8,7 @@ import { PUBLIC_API_URL, TRAVEL_API_URL, USER_API_URL } from '../app.config';
import { ListDto } from '../entities/list-dto';
import { User } from '../entities/user';
import { AuthService } from './auth.service';
import { TravelDto } from '../entities/travel-dto';
export type ApiCall<T> = (params?: { [param: string]: any }) => (Observable<ApiResponse<ListDto<T>>>);
@@ -28,7 +29,7 @@ export class ApiService {
}
getTravel = (travelId: TravelId) => {
return this.callApi<Travel>(PUBLIC_API_URL + '/travel', { travelId });
return this.callApi<TravelDto>(PUBLIC_API_URL + '/travel', { travelId });
}
getTravels = (params?: { [param: string]: any }) => {
@@ -47,6 +48,14 @@ export class ApiService {
return this.callApi<void>(TRAVEL_API_URL + '/edit', null, travel);
}
joinTravel = (travelId: TravelId) => {
return this.callApi<void>(TRAVEL_API_URL + '/join', { travelId });
}
leaveTravel = (travelId: TravelId) => {
return this.callApi<void>(TRAVEL_API_URL + '/leave', { travelId });
}
private callApi<T>(
url: string,
params?: { [param: string]: any },

View File

@@ -34,18 +34,6 @@ export class AuthService {
return User.fromClaims(claims);
}
canEditTravel(travel: Travel): boolean {
if (travel == null) {
return false;
}
const user = this.currentUser();
if (user == null) {
return false;
}
return user.admin || (user.id === travel.driverInfo.id);
}
private async configureAndTryLogin(): Promise<boolean> {
this.oauthService.configure(authConfig);
this.oauthService.setupAutomaticSilentRefresh();