Files
Calendar_Frontend/src/app/pages/admin/admin.component.ts
T
Paddy f82255961b
Jenkins Production Deployment
Sign in through the admin app instead of this one (#20)
Reviewed-on: #20
Co-authored-by: Patrick Müller <mail@pmueller.me>
Co-committed-by: Patrick Müller <mail@pmueller.me>
2026-09-06 21:13:12 +00:00

208 lines
5.2 KiB
TypeScript

import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {HttpErrorResponse} from '@angular/common/http';
import {ApiService} from '../../services/api.service';
import {UtilsService} from '../../services/utils.service';
import {AdminAuthService} from '../../services/admin-auth.service';
import {Event} from '../../models/event';
import {User} from '../../models/user';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
isLoggedIn: boolean = false;
events: Event[] = [];
selectedCalendar: string = '';
name: string = '';
eventFilter: string = 'future'; // Default value for filter
eventSorting: string = 'start_asc'; // Default value for sorting
/**
* Why the page is not showing events, when it is not.
*
* 'denied' and 'unavailable' are kept apart on purpose. Only a 401 is worth
* sending someone to the login page; bouncing them there for a 403 produces
* a loop where signing in succeeds and lands them straight back here, and
* bouncing them for an unreachable API produces the same loop with no way
* out at all.
*/
failure: 'denied' | 'unavailable' | null = null;
constructor(
private api: ApiService
) {
}
ngOnInit(): void {
this.api.me().pipe(takeUntil(this.destroy$)).subscribe({
next: (user: User) => {
this.isLoggedIn = true;
this.name = user.fullName;
UtilsService.saveNameToLocalStorage(user.fullName);
// Signed in, but not for this app. The API would answer 403 to
// every events call, so say so once instead of failing per call.
if (!user.apps.includes('calendar')) {
this.failure = 'denied';
return;
}
this.getEvents();
},
error: (error: HttpErrorResponse) => {
if (error.status === 401) {
AdminAuthService.goToLogin();
return;
}
this.failure = error.status === 403 ? 'denied' : 'unavailable';
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
getEvents(): void {
this.events = [];
if (this.selectedCalendar === '') {
return;
}
this.api.getEvents(this.selectedCalendar).pipe(takeUntil(this.destroy$)).subscribe({
next: (events: Event[]): void => {
for (let event of events) {
if (event.status !== 'DELETED') {
this.events.push({
...event,
startDateTime: new Date(event.startDateTime),
endDateTime: new Date(event.endDateTime),
createdDate: new Date(event.createdDate),
lastModifiedDate: new Date(event.lastModifiedDate)
});
}
}
this.filterEvents();
this.sortEvents();
},
// Without this a failed load is indistinguishable from an empty
// calendar - which is exactly what the old bundle looks like against
// the post-cutover API, and how a deploy in progress gets mistaken for
// lost data.
error: (error: HttpErrorResponse): void => {
if (error.status === 401) {
AdminAuthService.goToLogin();
return;
}
this.failure = error.status === 403 ? 'denied' : 'unavailable';
}
});
}
handleCalendarChange() {
this.getEvents();
}
getCalendarId(text: string): number {
switch (text) {
case 'public':
return 1;
case 'members':
return 2;
case 'choir':
return 4;
case 'management':
return 3;
case 'birthdays':
return 5;
default:
return -1;
}
}
filterEvents() {
this.events = this.events.filter((event) => {
return this.checkEventMeetsFilterCriteria(event);
});
}
checkEventMeetsFilterCriteria(event: Event): boolean {
// Always return birthdays regardless of date
if(event.calendarId === 5) {
return true;
}
switch (this.eventFilter) {
case '':
return true;
case 'all':
return true;
case 'future':
return this.reduceToDay(new Date(event.endDateTime)) >= this.reduceToDay(new Date());
case 'past':
return this.reduceToDay(new Date(event.endDateTime)) < this.reduceToDay(new Date());
default:
return true;
}
}
reduceToDay(date: Date): Date {
let dayOnlyDate: Date = new Date();
dayOnlyDate.setFullYear(date.getFullYear());
dayOnlyDate.setMonth(date.getMonth());
dayOnlyDate.setDate(date.getDate());
dayOnlyDate.setHours(0, 0, 0, 0);
return dayOnlyDate;
}
getUserName(): string {
return this.name;
}
sortEvents(): void {
this.events.sort((a, b) => {
switch (this.eventSorting) {
case '':
return 1;
case 'start_asc':
return a.startDateTime > b.startDateTime ? 1 : -1;
case 'start_desc':
return a.startDateTime > b.startDateTime ? -1 : 1;
case 'created_asc':
return a.createdDate > b.createdDate ? 1 : -1;
case 'created_desc':
return a.createdDate > b.createdDate ? -1 : 1;
default:
return 1;
}
});
}
/**
* There is no sign-in form here any more, and no account creation: accounts
* exist only by invitation from the admin app. Both are one redirect.
*/
signIn(): void {
AdminAuthService.goToLogin();
}
logout(): void {
UtilsService.clearName();
void AdminAuthService.signOut();
}
reload(): void {
window.location.reload();
}
}