Daniel 11 месяцев назад
Родитель
Сommit
474c558a94
18 измененных файлов: 83 добавлений и 31 удалений
  1. +1
    -1
      angular/openapi.json
  2. +6
    -0
      angular/openapi.yaml
  3. +2
    -0
      angular/src/app/_components/_abstract/abstract-data-form-component.ts
  4. +11
    -8
      angular/src/app/_components/_abstract/abstract-image-form-component.ts
  5. +2
    -0
      angular/src/app/_forms/apiForms.ts
  6. +2
    -9
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts
  7. +13
    -0
      angular/src/app/_views/user/user-form/user-form.component.html
  8. +21
    -6
      angular/src/app/_views/user/user-form/user-form.component.ts
  9. +1
    -0
      angular/src/app/_views/user/user-list/user-list.component.html
  10. +3
    -0
      angular/src/app/_views/user/user-list/user-list.component.ts
  11. +1
    -0
      angular/src/app/core/api/v1/model/user.ts
  12. +1
    -0
      angular/src/app/core/api/v1/model/userJsonld.ts
  13. +2
    -1
      angular/src/assets/i18n/en.json
  14. +4
    -1
      httpdocs/src/ApiResource/UserApi.php
  15. +2
    -3
      httpdocs/src/Entity/User.php
  16. +10
    -1
      httpdocs/src/Mapper/UserApiToEntityMapper.php
  17. +1
    -0
      httpdocs/src/Mapper/UserEntityToApiMapper.php
  18. +0
    -1
      httpdocs/src/Mapper/UserTripEntityToApiMapper.php

+ 1
- 1
angular/openapi.json
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 6
- 0
angular/openapi.yaml Просмотреть файл

@@ -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:


+ 2
- 0
angular/src/app/_components/_abstract/abstract-data-form-component.ts Просмотреть файл

@@ -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) {


+ 11
- 8
angular/src/app/_components/_abstract/abstract-image-form-component.ts Просмотреть файл

@@ -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);
}
} }

+ 2
- 0
angular/src/app/_forms/apiForms.ts Просмотреть файл

@@ -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, [])


+ 2
- 9
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts Просмотреть файл

@@ -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
- 0
angular/src/app/_views/user/user-form/user-form.component.html Просмотреть файл

@@ -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


+ 21
- 6
angular/src/app/_views/user/user-form/user-form.component.ts Просмотреть файл

@@ -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
); );


+ 1
- 0
angular/src/app/_views/user/user-list/user-list.component.html Просмотреть файл

@@ -5,5 +5,6 @@
[onNavigateToDetailsFunction]="navigateToUserDetails" [onNavigateToDetailsFunction]="navigateToUserDetails"
[onSortFunction]="onSortChange" [onSortFunction]="onSortChange"
[listColDefinitions]="listColDefinitions" [listColDefinitions]="listColDefinitions"
[dataFormComponent]="userFormComponent"
></app-list> ></app-list>
</div> </div>

+ 3
- 0
angular/src/app/_views/user/user-list/user-list.component.ts Просмотреть файл

@@ -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)]);
} }

} }

+ 1
- 0
angular/src/app/core/api/v1/model/user.ts Просмотреть файл

@@ -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;


+ 1
- 0
angular/src/app/core/api/v1/model/userJsonld.ts Просмотреть файл

@@ -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;


+ 2
- 1
angular/src/assets/i18n/en.json Просмотреть файл

@@ -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":
{ {


+ 4
- 1
httpdocs/src/ApiResource/UserApi.php Просмотреть файл

@@ -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 = [];


+ 2
- 3
httpdocs/src/Entity/User.php Просмотреть файл

@@ -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]


+ 10
- 1
httpdocs/src/Mapper/UserApiToEntityMapper.php Просмотреть файл

@@ -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));
} }


+ 1
- 0
httpdocs/src/Mapper/UserEntityToApiMapper.php Просмотреть файл

@@ -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;


+ 0
- 1
httpdocs/src/Mapper/UserTripEntityToApiMapper.php Просмотреть файл

@@ -61,7 +61,6 @@ class UserTripEntityToApiMapper implements MapperInterface
]); ]);
} }



return $dto; return $dto;
} }
} }

Загрузка…
Отмена
Сохранить