Sign in through the admin app instead of this one (#20)
Jenkins Production Deployment

Reviewed-on: #20
Co-authored-by: Patrick Müller <mail@pmueller.me>
Co-committed-by: Patrick Müller <mail@pmueller.me>
This commit was merged in pull request #20.
This commit is contained in:
2026-09-06 21:13:12 +00:00
committed by Patrick Müller
parent ded89d54f3
commit f82255961b
11 changed files with 316 additions and 279 deletions
+70 -86
View File
@@ -1,10 +1,11 @@
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 {Session} from '../../models/session';
import {User} from '../../models/user';
@Component({
@@ -19,15 +20,20 @@ export class AdminComponent implements OnInit, OnDestroy {
isLoggedIn: boolean = false;
events: Event[] = [];
selectedCalendar: string = '';
password: string = '';
name: string = '';
email: string = '';
eventFilter: string = 'future'; // Default value for filter
eventSorting: string = 'start_asc'; // Default value for sorting
isActive: boolean = false;
registerEmail: string = '';
registerPassword: string = '';
registerPasswordConfirm: string = '';
/**
* 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
@@ -35,16 +41,29 @@ export class AdminComponent implements OnInit, OnDestroy {
}
ngOnInit(): void {
if (UtilsService.getSessionInfoFromLocalStorage().sessionId !== -1) {
this.api.checkSession(UtilsService.getSessionInfoFromLocalStorage()).pipe(takeUntil(this.destroy$)).subscribe((user: User) => {
if(user.userId != null && user.userId !== -1) {
this.isLoggedIn = true;
this.name = user.fullName;
this.isActive = user.isActive;
this.getEvents();
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 {
@@ -59,20 +78,33 @@ export class AdminComponent implements OnInit, OnDestroy {
return;
}
this.api.getEvents(this.selectedCalendar).pipe(takeUntil(this.destroy$)).subscribe((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.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';
}
this.filterEvents();
this.sortEvents();
});
}
@@ -156,68 +188,20 @@ export class AdminComponent implements OnInit, OnDestroy {
});
}
login(): void {
this.api.login(this.email, this.password).pipe(takeUntil(this.destroy$)).subscribe((session: Session): void => {
if(session.sessionId != null && session.sessionId !== -1) {
UtilsService.saveSessionInfoToLocalStorage(session.sessionId, session.sessionKey);
// Get user info
this.api.checkSession(UtilsService.getSessionInfoFromLocalStorage()).pipe(takeUntil(this.destroy$)).subscribe((user: User) => {
if(user.userId != null && user.userId !== -1) {
this.isLoggedIn = true;
this.name = user.fullName;
this.isActive = user.isActive;
this.getEvents();
} else {
alert('Login unsuccessful. Please check if you provided the correct username and password.');
}
});
}
}, (error) => {
alert('Login unsuccessful. Reported problem from server: ' + error?.error?.message);
});
}
register(): void {
this.api.register(this.registerEmail, this.name, this.registerPassword).pipe(takeUntil(this.destroy$)).subscribe((session: Session): void => {
if(session.sessionId != null && session.sessionId !== -1) {
UtilsService.saveSessionInfoToLocalStorage(session.sessionId, session.sessionKey);
this.isLoggedIn = true;
this.getEvents();
alert('An email was sent to your Nachklang address. Please click the link in the email to activate your account. You can\'t use this application before the activation.');
} else {
alert('Registration unsuccessful. Please contact Patrick.');
}
}, (error) => {
alert('Registration unsuccessful. Reported problem from server: ' + error?.error?.message);
});
/**
* 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.clearSessionInfo();
this.isLoggedIn = false;
UtilsService.clearName();
void AdminAuthService.signOut();
}
checkUserInactive(): boolean {
return !this.isActive;
}
checkPasswordsMatch(): boolean {
return this.registerPassword === this.registerPasswordConfirm;
}
checkPasswordPolicy(): boolean {
let isLongEnough = this.registerPassword.length >= 12;
var lowercaseRegex = /[a-z]/g
let hasLowercase = lowercaseRegex.test(this.registerPassword);
var uppercaseRegex = /[A-Z]/g
let hasUppercase = uppercaseRegex.test(this.registerPassword);
var numberRegex = /[0-9]/g
let hasNumbers = numberRegex.test(this.registerPassword);
return isLongEnough && hasLowercase && hasUppercase && hasNumbers;
reload(): void {
window.location.reload();
}
}