From 16a6f51176f105621e171b7c2f15726edc585d23 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 29 Apr 2025 14:31:56 +0200 Subject: [PATCH] sorting fix, inactive login not possible etc --- .../search-select/search-select.component.ts | 1 + .../trip/trip-list/trip-list.component.ts | 8 ++++ angular/src/app/app-routing.module.ts | 22 +++++----- angular/src/assets/i18n/en.json | 3 +- httpdocs/src/ApiResource/UserTripApi.php | 3 +- httpdocs/src/Entity/Trip.php | 16 +++++++- httpdocs/src/Entity/User.php | 5 +++ .../AuthenticationSuccessListener.php | 5 --- httpdocs/src/Security/JwtAuthenticator.php | 41 +++++++++++-------- .../State/EntityClassDtoStateProcessor.php | 4 +- httpdocs/src/State/UserTripStateProcessor.php | 31 ++++++++++++++ 11 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 httpdocs/src/State/UserTripStateProcessor.php diff --git a/angular/src/app/_components/search-select/search-select.component.ts b/angular/src/app/_components/search-select/search-select.component.ts index 6d8f2ea..8155d23 100644 --- a/angular/src/app/_components/search-select/search-select.component.ts +++ b/angular/src/app/_components/search-select/search-select.component.ts @@ -120,6 +120,7 @@ export class SearchSelectComponent implements OnInit, AfterViewInit { text: 'model.zone', type: ListComponent.COLUMN_TYPE_TEXT, subResource: 'zone', + sortingSubResource: 'zone', field: 'name', sortable: true, filterType: FilterBarComponent.FILTER_TYPE_TEXT, diff --git a/angular/src/app/_views/trip/trip-list/trip-list.component.ts b/angular/src/app/_views/trip/trip-list/trip-list.component.ts index 02571af..32c1167 100644 --- a/angular/src/app/_views/trip/trip-list/trip-list.component.ts +++ b/angular/src/app/_views/trip/trip-list/trip-list.component.ts @@ -83,6 +83,14 @@ export class TripListComponent { sortable: true, filterType: FilterBarComponent.FILTER_TYPE_DATE, } as ListColDefinition, + { + name: 'completed', + text: 'trip.completed', + type: ListComponent.COLUMN_TYPE_BOOLEAN, + field: 'completed', + sortable: true, + filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN, + } as ListColDefinition, { name: 'createdAt', text: 'common.created_at', diff --git a/angular/src/app/app-routing.module.ts b/angular/src/app/app-routing.module.ts index a17972e..eed7f68 100644 --- a/angular/src/app/app-routing.module.ts +++ b/angular/src/app/app-routing.module.ts @@ -41,7 +41,7 @@ const routes: Routes = [ { path: ROUTE_DASHBOARD, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: '', component: DashboardComponent}, ] @@ -49,7 +49,7 @@ const routes: Routes = [ { path: ROUTE_BASE_DATA, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: '', component: BaseDataComponent}, ] @@ -57,7 +57,7 @@ const routes: Routes = [ { path: ROUTE_LOCATIONS, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: ':id', component: LocationDetailComponent}, ] @@ -65,7 +65,7 @@ const routes: Routes = [ { path: ROUTE_TRIPS, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: '', component: TripComponent}, ] @@ -73,7 +73,7 @@ const routes: Routes = [ { path: ROUTE_TRIPS, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: ':id', component: TripDetailComponent}, ] @@ -81,7 +81,7 @@ const routes: Routes = [ { path: ROUTE_USER_TRIPS, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: '', component: UserTripComponent}, ] @@ -89,7 +89,7 @@ const routes: Routes = [ { path: ROUTE_USER_TRIPS, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: ':id', component: UserTripDetailComponent}, ] @@ -97,7 +97,7 @@ const routes: Routes = [ { path: ROUTE_ZONES, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: ':id', component: ZoneDetailComponent}, ] @@ -105,7 +105,7 @@ const routes: Routes = [ { path: ROUTE_VESSELS, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: ':id', component: VesselDetailComponent}, ] @@ -113,7 +113,7 @@ const routes: Routes = [ { path: ROUTE_SHIPPING_COMPANIES, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: ':id', component: ShippingCompanyDetailComponent}, ] @@ -121,7 +121,7 @@ const routes: Routes = [ { path: ROUTE_PROFILE, component: TwoColumnComponent, - canActivate: [userGuard], + canActivate: [adminGuard], children: [ {path: '', component: ProfileComponent}, ] diff --git a/angular/src/assets/i18n/en.json b/angular/src/assets/i18n/en.json index adca224..577cedc 100644 --- a/angular/src/assets/i18n/en.json +++ b/angular/src/assets/i18n/en.json @@ -40,7 +40,8 @@ "save_itinerary": "Save itinerary", "assigned_users": "Assigned Pilots", "save_user_assignments": "Save user assignments", - "events": "Events" + "events": "Events", + "completed": "Completed" }, "user_trip": { diff --git a/httpdocs/src/ApiResource/UserTripApi.php b/httpdocs/src/ApiResource/UserTripApi.php index 0cc8dc8..90ec0ec 100644 --- a/httpdocs/src/ApiResource/UserTripApi.php +++ b/httpdocs/src/ApiResource/UserTripApi.php @@ -18,6 +18,7 @@ use App\Filter\CustomJsonFilter; use App\Filter\CustomJsonOrderFilter; use App\State\EntityClassDtoStateProcessor; use App\State\EntityToDtoStateProvider; +use App\State\UserTripStateProcessor; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Validator\Constraints\NotBlank; @@ -43,7 +44,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; ], security: 'is_granted("ROLE_USER")', provider: EntityToDtoStateProvider::class, - processor: EntityClassDtoStateProcessor::class, + processor: UserTripStateProcessor::class, stateOptions: new Options(entityClass: UserTrip::class), )] #[ApiFilter(SearchFilter::class, properties: [ diff --git a/httpdocs/src/Entity/Trip.php b/httpdocs/src/Entity/Trip.php index 566529a..b882100 100644 --- a/httpdocs/src/Entity/Trip.php +++ b/httpdocs/src/Entity/Trip.php @@ -156,8 +156,20 @@ class Trip return $this->completed; } - public function setCompleted(bool $completed): void - { + public function setCompleted(): void + { + $completed = true; + if (count($this->userTrips) <= 0) { + $completed = false; + } else { + /** @var UserTrip $userTrip */ + foreach ($this->userTrips as $userTrip) { + if (!$userTrip->isCompleted() || !$userTrip->isApproved()) { + $completed = false; + break; + } + } + } $this->completed = $completed; } diff --git a/httpdocs/src/Entity/User.php b/httpdocs/src/Entity/User.php index 54f9a1c..c0006a3 100644 --- a/httpdocs/src/Entity/User.php +++ b/httpdocs/src/Entity/User.php @@ -193,6 +193,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $this->isPilot = $isPilot; } + public function isAdmin() + { + return in_array(User::ROLE_ADMIN, $this->roles, true); + } + public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; diff --git a/httpdocs/src/EventListener/AuthenticationSuccessListener.php b/httpdocs/src/EventListener/AuthenticationSuccessListener.php index a1156e0..e8fd950 100644 --- a/httpdocs/src/EventListener/AuthenticationSuccessListener.php +++ b/httpdocs/src/EventListener/AuthenticationSuccessListener.php @@ -1,9 +1,4 @@ - * @date 18.01.24 - */ - namespace App\EventListener; diff --git a/httpdocs/src/Security/JwtAuthenticator.php b/httpdocs/src/Security/JwtAuthenticator.php index 215b75d..d85f839 100644 --- a/httpdocs/src/Security/JwtAuthenticator.php +++ b/httpdocs/src/Security/JwtAuthenticator.php @@ -1,15 +1,8 @@ getContent(), true); - if (!$data) { - throw new CustomUserMessageAuthenticationException('Invalid JSON.'); + throw new CustomUserMessageAuthenticationException('Ungültige JSON-Daten.'); } - $email = $data['email'] ?? ''; + $email = $data['email'] ?? ''; $password = $data['password'] ?? ''; + $userBadge = new UserBadge($email, function(string $userIdentifier) { + $user = $this->em + ->getRepository(User::class) + ->findOneBy(['email' => $userIdentifier]); + + if (!$user) { + // Standardnachricht für nicht gefundene E-Mail + throw new CustomUserMessageAuthenticationException('Invalid email or password.'); + } + + if (!$user->isActive()) { + // custom Message, erscheint im JSON-Response + throw new CustomUserMessageAuthenticationException('Your account is currently deactivated.'); + } + + return $user; + }); + return new Passport( - new UserBadge($email), + $userBadge, new PasswordCredentials($password) ); } + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { return new JsonResponse([ diff --git a/httpdocs/src/State/EntityClassDtoStateProcessor.php b/httpdocs/src/State/EntityClassDtoStateProcessor.php index 403f873..0dec3ce 100644 --- a/httpdocs/src/State/EntityClassDtoStateProcessor.php +++ b/httpdocs/src/State/EntityClassDtoStateProcessor.php @@ -8,6 +8,7 @@ use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata\DeleteOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfonycasts\MicroMapper\MicroMapperInterface; @@ -16,7 +17,8 @@ class EntityClassDtoStateProcessor implements ProcessorInterface public function __construct( #[Autowire(service: PersistProcessor::class)] private ProcessorInterface $persistProcessor, #[Autowire(service: RemoveProcessor::class)] private ProcessorInterface $removeProcessor, - private MicroMapperInterface $microMapper + private MicroMapperInterface $microMapper, + protected EntityManagerInterface $em ) {} diff --git a/httpdocs/src/State/UserTripStateProcessor.php b/httpdocs/src/State/UserTripStateProcessor.php new file mode 100644 index 0000000..5ca78bc --- /dev/null +++ b/httpdocs/src/State/UserTripStateProcessor.php @@ -0,0 +1,31 @@ + + * @date 29.04.25 + */ + + +namespace App\State; + + +use ApiPlatform\Metadata\Operation; +use App\Entity\Trip; + +class UserTripStateProcessor extends EntityClassDtoStateProcessor +{ + + + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) + { + $res = parent::process($data, $operation, $uriVariables, $context); + + /** @var Trip $trip */ + $trip = $this->em->getRepository(Trip::class)->find($data->tripIri->id); + $trip->setCompleted(); + $this->em->flush(); + + return $res; + } + + +} \ No newline at end of file