| @@ -2868,6 +2868,9 @@ components: | |||||
| type: | type: | ||||
| - string | - string | ||||
| - 'null' | - 'null' | ||||
| isAdmin: | |||||
| writeOnly: true | |||||
| type: boolean | |||||
| active: | active: | ||||
| type: boolean | type: boolean | ||||
| roles: | roles: | ||||
| @@ -2956,6 +2959,9 @@ components: | |||||
| type: | type: | ||||
| - string | - string | ||||
| - 'null' | - 'null' | ||||
| isAdmin: | |||||
| writeOnly: true | |||||
| type: boolean | |||||
| active: | active: | ||||
| type: boolean | type: boolean | ||||
| roles: | roles: | ||||
| @@ -41,6 +41,8 @@ export abstract class AbstractDataFormComponent<T extends { [key: string]: any } | |||||
| } | } | ||||
| ngOnInit(): void { | ngOnInit(): void { | ||||
| this.form.reset(); | |||||
| if (this.data) { | if (this.data) { | ||||
| this.form.patchValue(this.data); | this.form.patchValue(this.data); | ||||
| } else if (this.mode === FormMode.Create) { | } else if (this.mode === FormMode.Create) { | ||||
| @@ -8,22 +8,19 @@ import { ModalStatus } from '@app/_helpers/modal.states'; | |||||
| @Directive() | @Directive() | ||||
| export abstract class AbstractImageFormComponent<T extends { [key: string]: any }> extends AbstractDataFormComponent<T> { | export abstract class AbstractImageFormComponent<T extends { [key: string]: any }> extends AbstractDataFormComponent<T> { | ||||
| // Gemeinsame Eigenschaften für Komponenten mit Bildupload | |||||
| selectedFile: File | null = null; | selectedFile: File | null = null; | ||||
| imageToDelete: string | null = null; | imageToDelete: string | null = null; | ||||
| @ViewChild('imageUpload') imageUpload!: ImageUploadComponent; | @ViewChild('imageUpload') imageUpload!: ImageUploadComponent; | ||||
| // Abstrakte Eigenschaften, die von abgeleiteten Klassen implementiert werden müssen | |||||
| abstract get imageIriControlName(): string; | |||||
| abstract get imageDbId(): string | number | undefined | null; | |||||
| constructor( | constructor( | ||||
| protected imageUploadService: ImageUploadService, | protected imageUploadService: ImageUploadService, | ||||
| formGroup: FormGroup, | formGroup: FormGroup, | ||||
| createFunction: ((data: T) => any) | undefined, | createFunction: ((data: T) => any) | undefined, | ||||
| updateFunction: (id: string | number, data: T) => any, | updateFunction: (id: string | number, data: T) => any, | ||||
| deleteFunction: (id: string | number) => any, | deleteFunction: (id: string | number) => any, | ||||
| protected imageIriControlName: string, // neuer Parameter | |||||
| protected imageDbIdPath: string, | |||||
| ...args: any[] | ...args: any[] | ||||
| ) { | ) { | ||||
| super(formGroup, createFunction, updateFunction, deleteFunction, ...args); | super(formGroup, createFunction, updateFunction, deleteFunction, ...args); | ||||
| @@ -75,7 +72,7 @@ export abstract class AbstractImageFormComponent<T extends { [key: string]: any | |||||
| if (this.selectedFile && this.imageToDelete) { | if (this.selectedFile && this.imageToDelete) { | ||||
| // Fall 1a: Ein neues Bild wurde ausgewählt UND ein altes soll gelöscht werden | // Fall 1a: Ein neues Bild wurde ausgewählt UND ein altes soll gelöscht werden | ||||
| // Zuerst das alte Bild löschen, dann das neue hochladen | // Zuerst das alte Bild löschen, dann das neue hochladen | ||||
| const dbId = this.imageDbId; | |||||
| const dbId = this.getNestedProperty(this.data, this.imageDbIdPath); | |||||
| if (dbId) { | if (dbId) { | ||||
| this.imageUploadService.deleteImage(dbId).subscribe({ | this.imageUploadService.deleteImage(dbId).subscribe({ | ||||
| next: (success) => { | next: (success) => { | ||||
| @@ -93,9 +90,10 @@ export abstract class AbstractImageFormComponent<T extends { [key: string]: any | |||||
| } else if (this.selectedFile) { | } else if (this.selectedFile) { | ||||
| // Fall 1b: Nur ein neues Bild wurde ausgewählt (kein altes vorhanden) | // Fall 1b: Nur ein neues Bild wurde ausgewählt (kein altes vorhanden) | ||||
| this.uploadNewFile(); | this.uploadNewFile(); | ||||
| } else if (this.imageToDelete && this.imageDbId) { | |||||
| } else if (this.imageToDelete) { | |||||
| // Fall 2: Nur ein bestehendes Bild soll gelöscht werden | // Fall 2: Nur ein bestehendes Bild soll gelöscht werden | ||||
| this.imageUploadService.deleteImage(this.imageDbId).subscribe({ | |||||
| const dbId = this.getNestedProperty(this.data, this.imageDbIdPath); | |||||
| this.imageUploadService.deleteImage(dbId).subscribe({ | |||||
| next: (success) => { | next: (success) => { | ||||
| this.imageToDelete = null; | this.imageToDelete = null; | ||||
| super.onSubmit(); | super.onSubmit(); | ||||
| @@ -133,4 +131,9 @@ export abstract class AbstractImageFormComponent<T extends { [key: string]: any | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| protected getNestedProperty(obj: any, path: string): any { | |||||
| if (!obj || !path) return null; | |||||
| return path.split('.').reduce((o, p) => (o ? o[p] : null), obj); | |||||
| } | |||||
| } | } | ||||
| @@ -123,6 +123,7 @@ export const userForm = new FormGroup({ | |||||
| imageUrl: new FormControl(null, []), | imageUrl: new FormControl(null, []), | ||||
| fullName: new FormControl(null, []), | fullName: new FormControl(null, []), | ||||
| password: new FormControl(null, []), | password: new FormControl(null, []), | ||||
| isAdmin: new FormControl(null, []), | |||||
| active: new FormControl(null, [Validators.required]), | active: new FormControl(null, [Validators.required]), | ||||
| roles: new FormControl(null, []), | roles: new FormControl(null, []), | ||||
| createdAt: new FormControl(null, []) | createdAt: new FormControl(null, []) | ||||
| @@ -139,6 +140,7 @@ export const userJsonldForm = new FormGroup({ | |||||
| imageUrl: new FormControl(null, []), | imageUrl: new FormControl(null, []), | ||||
| fullName: new FormControl(null, []), | fullName: new FormControl(null, []), | ||||
| password: new FormControl(null, []), | password: new FormControl(null, []), | ||||
| isAdmin: new FormControl(null, []), | |||||
| active: new FormControl(null, [Validators.required]), | active: new FormControl(null, [Validators.required]), | ||||
| roles: new FormControl(null, []), | roles: new FormControl(null, []), | ||||
| createdAt: new FormControl(null, []) | createdAt: new FormControl(null, []) | ||||
| @@ -17,15 +17,6 @@ export class UserTripFormComponent extends AbstractImageFormComponent<UserTripJs | |||||
| protected readonly userTripForm = userTripForm; | protected readonly userTripForm = userTripForm; | ||||
| // Implementierung der abstrakten Eigenschaften | |||||
| get imageIriControlName(): string { | |||||
| return 'signatureIri'; | |||||
| } | |||||
| get imageDbId(): string | undefined | null | number { | |||||
| return this.data?.signature?.dbId; | |||||
| } | |||||
| constructor( | constructor( | ||||
| private userTripService: UserTripService, | private userTripService: UserTripService, | ||||
| imageUploadService: ImageUploadService, | imageUploadService: ImageUploadService, | ||||
| @@ -43,6 +34,8 @@ export class UserTripFormComponent extends AbstractImageFormComponent<UserTripJs | |||||
| appHelperService.convertJsonldToJson(data) | appHelperService.convertJsonldToJson(data) | ||||
| ), | ), | ||||
| (id: string | number) => userTripService.userTripsIdDelete(id.toString()), | (id: string | number) => userTripService.userTripsIdDelete(id.toString()), | ||||
| 'signatureIri', | |||||
| 'signature.dbId', | |||||
| translateService, | translateService, | ||||
| router | router | ||||
| ); | ); | ||||
| @@ -13,6 +13,11 @@ | |||||
| <input type="text" class="form-control" id="email" formControlName="email"/> | <input type="text" class="form-control" id="email" formControlName="email"/> | ||||
| </div> | </div> | ||||
| <div class="mb-3"> | |||||
| <label for="firstName" class="form-label">{{ 'users.firstname' | translate }}:</label> | |||||
| <input type="text" class="form-control" id="firstName" formControlName="firstName"/> | |||||
| </div> | |||||
| <div class="mb-3"> | <div class="mb-3"> | ||||
| <label for="lastName" class="form-label">{{ 'users.lastname' | translate }}:</label> | <label for="lastName" class="form-label">{{ 'users.lastname' | translate }}:</label> | ||||
| <input type="text" class="form-control" id="lastName" formControlName="lastName"/> | <input type="text" class="form-control" id="lastName" formControlName="lastName"/> | ||||
| @@ -36,6 +41,14 @@ | |||||
| </label> | </label> | ||||
| </div> | </div> | ||||
| <div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget"> | |||||
| <p class="form-label">{{ 'users.is_admin' | translate }}:</p> | |||||
| <label class="switch"> | |||||
| <input type="checkbox" formControlName="isAdmin"> | |||||
| <span class="slider round"></span> | |||||
| </label> | |||||
| </div> | |||||
| <!-- Verwende die neue ImageUpload-Komponente --> | <!-- Verwende die neue ImageUpload-Komponente --> | ||||
| <app-image-upload | <app-image-upload | ||||
| #imageUpload | #imageUpload | ||||
| @@ -7,6 +7,7 @@ import { ROUTE_USERS } from "@app/app-routing.module"; | |||||
| import { userForm } from "@app/_forms/apiForms"; | import { userForm } from "@app/_forms/apiForms"; | ||||
| import { AbstractImageFormComponent } from "@app/_components/_abstract/abstract-image-form-component"; | import { AbstractImageFormComponent } from "@app/_components/_abstract/abstract-image-form-component"; | ||||
| import { ImageUploadService } from "@app/_services/image-upload.service"; | import { ImageUploadService } from "@app/_services/image-upload.service"; | ||||
| import {Validators} from "@angular/forms"; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-user-form', | selector: 'app-user-form', | ||||
| @@ -17,13 +18,25 @@ export class UserFormComponent extends AbstractImageFormComponent<UserJsonld> { | |||||
| protected readonly userForm = userForm; | protected readonly userForm = userForm; | ||||
| // Implementierung der abstrakten Eigenschaften | |||||
| get imageIriControlName(): string { | |||||
| return 'imageIri'; | |||||
| } | |||||
| override ngOnInit(): void { | |||||
| super.ngOnInit(); | |||||
| // Wenn wir einen neuen Benutzer erstellen, initialisiere active mit false | |||||
| if (!this.isEditMode()) { | |||||
| this.form.get('active')?.setValue(false); | |||||
| this.form.get('isAdmin')?.setValue(false); | |||||
| get imageDbId(): string | number | undefined | null { | |||||
| return this.data?.image?.dbId; | |||||
| // password-Feld required machen | |||||
| const passwordControl = this.form.get('password'); | |||||
| if (passwordControl) { | |||||
| // Bestehende Validatoren erhalten | |||||
| const currentValidators = passwordControl.validator ? [passwordControl.validator] : []; | |||||
| // Validators.required hinzufügen | |||||
| passwordControl.setValidators([...currentValidators, Validators.required]); | |||||
| // Validatoren aktualisieren | |||||
| passwordControl.updateValueAndValidity(); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| constructor( | constructor( | ||||
| @@ -43,6 +56,8 @@ export class UserFormComponent extends AbstractImageFormComponent<UserJsonld> { | |||||
| appHelperService.convertJsonldToJson(data) | appHelperService.convertJsonldToJson(data) | ||||
| ), | ), | ||||
| (id: string | number) => userService.usersIdDelete(id.toString()), | (id: string | number) => userService.usersIdDelete(id.toString()), | ||||
| 'imageIri', | |||||
| 'image.dbId', | |||||
| translateService, | translateService, | ||||
| router | router | ||||
| ); | ); | ||||
| @@ -5,5 +5,6 @@ | |||||
| [onNavigateToDetailsFunction]="navigateToUserDetails" | [onNavigateToDetailsFunction]="navigateToUserDetails" | ||||
| [onSortFunction]="onSortChange" | [onSortFunction]="onSortChange" | ||||
| [listColDefinitions]="listColDefinitions" | [listColDefinitions]="listColDefinitions" | ||||
| [dataFormComponent]="userFormComponent" | |||||
| ></app-list> | ></app-list> | ||||
| </div> | </div> | ||||
| @@ -8,6 +8,7 @@ import {ListComponent} from "@app/_components/list/list.component"; | |||||
| import {ListColDefinition} from "@app/_components/list/list-col-definition"; | import {ListColDefinition} from "@app/_components/list/list-col-definition"; | ||||
| import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-function-type"; | import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-function-type"; | ||||
| import {FilterBarComponent} from "@app/_components/filter-bar/filter-bar.component"; | import {FilterBarComponent} from "@app/_components/filter-bar/filter-bar.component"; | ||||
| import {UserFormComponent} from "@app/_views/user/user-form/user-form.component"; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-user-list', | selector: 'app-user-list', | ||||
| @@ -18,6 +19,7 @@ export class UserListComponent implements OnInit, AfterViewInit { | |||||
| @ViewChild("listComponent", {static: false}) listComponent!: ListComponent; | @ViewChild("listComponent", {static: false}) listComponent!: ListComponent; | ||||
| protected readonly userFormComponent = UserFormComponent; | |||||
| protected firstNameOrderFilter: OrderFilter; | protected firstNameOrderFilter: OrderFilter; | ||||
| protected lastNameOrderFilter: OrderFilter; | protected lastNameOrderFilter: OrderFilter; | ||||
| protected listColDefinitions!: ListColDefinition[]; | protected listColDefinitions!: ListColDefinition[]; | ||||
| @@ -110,4 +112,5 @@ export class UserListComponent implements OnInit, AfterViewInit { | |||||
| const user: UserJsonld = element as UserJsonld; | const user: UserJsonld = element as UserJsonld; | ||||
| this.router.navigate(['/user', this.appHelperService.extractId(user.id)]); | this.router.navigate(['/user', this.appHelperService.extractId(user.id)]); | ||||
| } | } | ||||
| } | } | ||||
| @@ -28,6 +28,7 @@ export interface User { | |||||
| * The plaintext password when being set or changed. | * The plaintext password when being set or changed. | ||||
| */ | */ | ||||
| password?: string | null; | password?: string | null; | ||||
| isAdmin?: boolean; | |||||
| active: boolean; | active: boolean; | ||||
| roles?: Array<string>; | roles?: Array<string>; | ||||
| readonly createdAt?: string | null; | readonly createdAt?: string | null; | ||||
| @@ -33,6 +33,7 @@ export interface UserJsonld { | |||||
| * The plaintext password when being set or changed. | * The plaintext password when being set or changed. | ||||
| */ | */ | ||||
| password?: string | null; | password?: string | null; | ||||
| isAdmin?: boolean; | |||||
| active: boolean; | active: boolean; | ||||
| roles?: Array<string>; | roles?: Array<string>; | ||||
| readonly createdAt?: string | null; | readonly createdAt?: string | null; | ||||
| @@ -123,7 +123,8 @@ | |||||
| "pilotIdNo": "#Pilot id", | "pilotIdNo": "#Pilot id", | ||||
| "password": "Password", | "password": "Password", | ||||
| "active": "Active", | "active": "Active", | ||||
| "image": "Image" | |||||
| "image": "Image", | |||||
| "is_admin": "Is admin" | |||||
| }, | }, | ||||
| "form": | "form": | ||||
| { | { | ||||
| @@ -108,7 +108,10 @@ class UserApi | |||||
| public ?string $password = null; | public ?string $password = null; | ||||
| #[Assert\NotNull] | #[Assert\NotNull] | ||||
| public bool $active; | |||||
| public bool $isAdmin = false; | |||||
| #[Assert\NotNull] | |||||
| public bool $active = false; | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public array $roles = []; | public array $roles = []; | ||||
| @@ -3,9 +3,6 @@ | |||||
| namespace App\Entity; | namespace App\Entity; | ||||
| use App\Repository\UserRepository; | use App\Repository\UserRepository; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | |||||
| use Doctrine\Common\Collections\Collection; | |||||
| use Doctrine\DBAL\Types\Types; | |||||
| use Doctrine\ORM\Mapping as ORM; | use Doctrine\ORM\Mapping as ORM; | ||||
| use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; | use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; | ||||
| use Symfony\Component\Security\Core\User\UserInterface; | use Symfony\Component\Security\Core\User\UserInterface; | ||||
| @@ -14,6 +11,8 @@ use Symfony\Component\Security\Core\User\UserInterface; | |||||
| #[ORM\Table(name: '`user`')] | #[ORM\Table(name: '`user`')] | ||||
| class User implements UserInterface, PasswordAuthenticatedUserInterface | class User implements UserInterface, PasswordAuthenticatedUserInterface | ||||
| { | { | ||||
| public const ROLE_ADMIN = "ROLE_ADMIN"; | |||||
| #[ORM\Id] | #[ORM\Id] | ||||
| #[ORM\GeneratedValue] | #[ORM\GeneratedValue] | ||||
| #[ORM\Column] | #[ORM\Column] | ||||
| @@ -4,7 +4,6 @@ namespace App\Mapper; | |||||
| use App\ApiResource\UserApi; | use App\ApiResource\UserApi; | ||||
| use App\Entity\User; | use App\Entity\User; | ||||
| use App\Entity\Posting; | |||||
| use App\Repository\MediaObjectRepository; | use App\Repository\MediaObjectRepository; | ||||
| use App\Repository\UserRepository; | use App\Repository\UserRepository; | ||||
| use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; | use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; | ||||
| @@ -47,6 +46,16 @@ class UserApiToEntityMapper implements MapperInterface | |||||
| $entity->setFirstName($dto->firstName); | $entity->setFirstName($dto->firstName); | ||||
| $entity->setLastName($dto->lastName); | $entity->setLastName($dto->lastName); | ||||
| $entity->setActive($dto->active); | $entity->setActive($dto->active); | ||||
| $roles = $entity->getRoles(); | |||||
| if ($dto->isAdmin && !in_array(User::ROLE_ADMIN, $roles, true)) { | |||||
| $entity->setRoles([User::ROLE_ADMIN]); | |||||
| } | |||||
| if (!$dto->isAdmin && in_array(User::ROLE_ADMIN, $roles, true)) { | |||||
| $entity->setRoles([]); | |||||
| } | |||||
| if ($dto->password) { | if ($dto->password) { | ||||
| $entity->setPassword($this->userPasswordHasher->hashPassword($entity, $dto->password)); | $entity->setPassword($this->userPasswordHasher->hashPassword($entity, $dto->password)); | ||||
| } | } | ||||
| @@ -44,6 +44,7 @@ class UserEntityToApiMapper implements MapperInterface | |||||
| $dto->firstName = $entity->getFirstName(); | $dto->firstName = $entity->getFirstName(); | ||||
| $dto->lastName = $entity->getLastName(); | $dto->lastName = $entity->getLastName(); | ||||
| $dto->roles = $entity->getRoles(); | $dto->roles = $entity->getRoles(); | ||||
| $dto->isAdmin = in_array(User::ROLE_ADMIN, $entity->getRoles(), true); | |||||
| $dto->active = $entity->isActive(); | $dto->active = $entity->isActive(); | ||||
| $dto->imageIri = $dto->image = null; | $dto->imageIri = $dto->image = null; | ||||
| @@ -61,7 +61,6 @@ class UserTripEntityToApiMapper implements MapperInterface | |||||
| ]); | ]); | ||||
| } | } | ||||
| return $dto; | return $dto; | ||||
| } | } | ||||
| } | } | ||||