| @@ -2317,28 +2317,26 @@ components: | |||||
| description: '' | description: '' | ||||
| deprecated: false | deprecated: false | ||||
| properties: | properties: | ||||
| dbId: | |||||
| id: | |||||
| readOnly: true | readOnly: true | ||||
| type: | |||||
| - integer | |||||
| - 'null' | |||||
| contentUrl: | |||||
| externalDocs: | |||||
| url: 'https://schema.org/contentUrl' | |||||
| type: integer | |||||
| file: | |||||
| type: | type: | ||||
| - string | - string | ||||
| - 'null' | - 'null' | ||||
| format: binary | |||||
| filePath: | filePath: | ||||
| readOnly: true | |||||
| type: | type: | ||||
| - string | - string | ||||
| - 'null' | - 'null' | ||||
| createdBy: | |||||
| $ref: '#/components/schemas/User' | |||||
| createdAt: | createdAt: | ||||
| readOnly: true | readOnly: true | ||||
| type: | |||||
| - string | |||||
| - 'null' | |||||
| type: string | |||||
| format: date-time | format: date-time | ||||
| required: | |||||
| - file | |||||
| MediaObject.jsonld: | MediaObject.jsonld: | ||||
| type: object | type: object | ||||
| description: '' | description: '' | ||||
| @@ -2676,11 +2674,11 @@ components: | |||||
| lastName: | lastName: | ||||
| type: string | type: string | ||||
| image: | image: | ||||
| type: | |||||
| - string | |||||
| - 'null' | |||||
| format: iri-reference | |||||
| example: 'https://example.com/' | |||||
| anyOf: | |||||
| - | |||||
| $ref: '#/components/schemas/MediaObject' | |||||
| - | |||||
| type: 'null' | |||||
| imageUrl: | imageUrl: | ||||
| readOnly: true | readOnly: true | ||||
| type: | type: | ||||
| @@ -2758,11 +2756,11 @@ components: | |||||
| lastName: | lastName: | ||||
| type: string | type: string | ||||
| image: | image: | ||||
| type: | |||||
| - string | |||||
| - 'null' | |||||
| format: iri-reference | |||||
| example: 'https://example.com/' | |||||
| anyOf: | |||||
| - | |||||
| $ref: '#/components/schemas/MediaObject.jsonld' | |||||
| - | |||||
| type: 'null' | |||||
| imageUrl: | imageUrl: | ||||
| readOnly: true | readOnly: true | ||||
| type: | type: | ||||
| @@ -2836,6 +2834,7 @@ components: | |||||
| required: | required: | ||||
| - trip | - trip | ||||
| - user | - user | ||||
| - completed | |||||
| UserTrip.jsonld: | UserTrip.jsonld: | ||||
| type: object | type: object | ||||
| description: '' | description: '' | ||||
| @@ -2900,6 +2899,7 @@ components: | |||||
| required: | required: | ||||
| - trip | - trip | ||||
| - user | - user | ||||
| - completed | |||||
| UserTripEvent: | UserTripEvent: | ||||
| type: object | type: object | ||||
| description: '' | description: '' | ||||
| @@ -32,9 +32,10 @@ export const locationJsonldForm = new FormGroup({ | |||||
| }); | }); | ||||
| export const mediaObjectForm = new FormGroup({ | export const mediaObjectForm = new FormGroup({ | ||||
| dbId: new FormControl(null, []), | |||||
| contentUrl: new FormControl(null, []), | |||||
| id: new FormControl(null, []), | |||||
| file: new FormControl(null, [Validators.required]), | |||||
| filePath: new FormControl(null, []), | filePath: new FormControl(null, []), | ||||
| createdBy: new FormControl(null, []), | |||||
| createdAt: new FormControl(null, []) | createdAt: new FormControl(null, []) | ||||
| }); | }); | ||||
| @@ -140,7 +141,7 @@ export const userTripForm = new FormGroup({ | |||||
| trip: new FormControl(null, [Validators.required]), | trip: new FormControl(null, [Validators.required]), | ||||
| user: new FormControl(null, [Validators.required]), | user: new FormControl(null, [Validators.required]), | ||||
| captainName: new FormControl(null, []), | captainName: new FormControl(null, []), | ||||
| completed: new FormControl(null, []), | |||||
| completed: new FormControl(null, [Validators.required]), | |||||
| signature: new FormControl(null, []), | signature: new FormControl(null, []), | ||||
| signatureUrl: new FormControl(null, []), | signatureUrl: new FormControl(null, []), | ||||
| completedDate: new FormControl(null, []), | completedDate: new FormControl(null, []), | ||||
| @@ -152,7 +153,7 @@ export const userTripJsonldForm = new FormGroup({ | |||||
| trip: new FormControl(null, [Validators.required]), | trip: new FormControl(null, [Validators.required]), | ||||
| user: new FormControl(null, [Validators.required]), | user: new FormControl(null, [Validators.required]), | ||||
| captainName: new FormControl(null, []), | captainName: new FormControl(null, []), | ||||
| completed: new FormControl(null, []), | |||||
| completed: new FormControl(null, [Validators.required]), | |||||
| signature: new FormControl(null, []), | signature: new FormControl(null, []), | ||||
| signatureUrl: new FormControl(null, []), | signatureUrl: new FormControl(null, []), | ||||
| completedDate: new FormControl(null, []), | completedDate: new FormControl(null, []), | ||||
| @@ -1,41 +1,43 @@ | |||||
| <div class="spt-container"> | <div class="spt-container"> | ||||
| <div class="spt-form"> | <div class="spt-form"> | ||||
| <form [formGroup]="userTripForm" (ngSubmit)="onSubmit()"> | |||||
| @if (data !== undefined) { | |||||
| <form [formGroup]="userTripForm" (ngSubmit)="onSubmit()"> | |||||
| <input type="hidden" formControlName="trip" /> | |||||
| <input type="hidden" formControlName="user" /> | |||||
| <input type="hidden" formControlName="signature" /> | |||||
| <div class="mb-3"> | |||||
| <label for="captainName" class="form-label">{{ 'trip.captain_name' | translate }}:</label> | |||||
| <input type="text" class="form-control" id="captainName" formControlName="captainName"/> | |||||
| </div> | |||||
| <div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget"> | |||||
| <p class="form-label">{{ 'user_trip.completed' | translate }}:</p> | |||||
| <label class="switch"> | |||||
| <input type="checkbox" formControlName="completed" [disabled]="data ? data.completed : false"> | |||||
| <span class="slider round"></span> | |||||
| </label> | |||||
| </div> | |||||
| <input type="hidden" formControlName="trip" /> | |||||
| <input type="hidden" formControlName="user" /> | |||||
| <input type="hidden" formControlName="signature" /> | |||||
| <div class="mb-3"> | |||||
| <label for="captainName" class="form-label">{{ 'trip.captain_name' | translate }}:</label> | |||||
| <input type="text" class="form-control" id="captainName" formControlName="captainName"/> | |||||
| </div> | |||||
| <div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget"> | |||||
| <p class="form-label">{{ 'user_trip.completed' | translate }}:</p> | |||||
| <label class="switch"> | |||||
| <input type="checkbox" formControlName="completed" [disabled]="data.completed"> | |||||
| <span class="slider round"></span> | |||||
| </label> | |||||
| </div> | |||||
| <!-- Neues File-Upload-Feld für MediaObject --> | |||||
| <div class="mb-3"> | |||||
| <label for="mediaFile" class="form-label">{{ 'user_trip.signature' | translate }}:</label> | |||||
| <input type="file" class="form-control" id="mediaFile" (change)="onFileSelected($event)"/> | |||||
| @if (selectedFile) { | |||||
| <small class="text-muted">{{ selectedFile.name }}</small> | |||||
| } | |||||
| @if (data.signatureUrl) { | |||||
| <div class="mt-1"> | |||||
| <img [src]="data.signatureUrl" alt="Signatur" class="img-fluid" /> | |||||
| </div> | |||||
| } | |||||
| </div> | |||||
| </form> | |||||
| } | |||||
| <!-- Neues File-Upload-Feld für MediaObject --> | |||||
| <div class="mb-3"> | |||||
| <label for="mediaFile" class="form-label">{{ 'user_trip.signature' | translate }}:</label> | |||||
| <input type="file" class="form-control" id="mediaFile" (change)="onFileSelected($event)"/> | |||||
| @if (selectedFile) { | |||||
| <small class="text-muted">{{ selectedFile.name }}</small> | |||||
| } | |||||
| @if (data?.signature?.contentUrl) { | |||||
| <div class="mt-2"> | |||||
| <small>{{ 'user_trip.current_file' | translate }}: {{ getFileNameFromUrl(data?.signature?.contentUrl) }}</small> | |||||
| </div> | |||||
| } | |||||
| </div> | |||||
| </form> | |||||
| </div> | </div> | ||||
| <div class="flex gap-2"> | <div class="flex gap-2"> | ||||
| <!-- <button type="submit" class="btn btn-primary" [disabled]="form.invalid" (click)="onSubmit()">--> | |||||
| <button type="submit" class="btn btn-primary" (click)="onSubmit()"> | |||||
| <button type="submit" class="btn btn-primary" [disabled]="form.invalid" (click)="onSubmit()"> | |||||
| {{ 'basic.save' | translate }} | {{ 'basic.save' | translate }} | ||||
| </button> | </button> | ||||
| @@ -5,7 +5,7 @@ import { | |||||
| FormSubmitEvent | FormSubmitEvent | ||||
| } from "@app/_components/_abstract/abstract-data-form-component"; | } from "@app/_components/_abstract/abstract-data-form-component"; | ||||
| import { | import { | ||||
| LocationService, MediaObjectJsonld, MediaObjectService, TripJsonld, | |||||
| LocationService, MediaObjectJsonld, MediaObjectService, TripJsonld, UserTrip, | |||||
| UserTripJsonld, | UserTripJsonld, | ||||
| UserTripService, | UserTripService, | ||||
| VesselService | VesselService | ||||
| @@ -74,21 +74,8 @@ export class UserTripFormComponent extends AbstractDataFormComponent<UserTripJso | |||||
| } | } | ||||
| override onSubmit(): void { | override onSubmit(): void { | ||||
| if (!this.form.valid) { | |||||
| console.log('Form is invalid:', this.form.errors); | |||||
| Object.keys(this.form.controls).forEach(key => { | |||||
| const control = this.form.get(key); | |||||
| if (control?.invalid) { | |||||
| console.log(`Control '${key}' is invalid:`, control.errors); | |||||
| } | |||||
| }); | |||||
| return; | |||||
| } | |||||
| // Wenn keine Datei ausgewählt wurde, rufe direkt die Elternmethode auf | |||||
| if (!this.selectedFile) { | if (!this.selectedFile) { | ||||
| // Wenn keine Datei ausgewählt wurde und keine vorhandene Signature existiert, | |||||
| // setzen wir sie auf undefined (nicht null) | |||||
| const currentValue = this.form.get('signature')?.value; | const currentValue = this.form.get('signature')?.value; | ||||
| if (currentValue === null) { | if (currentValue === null) { | ||||
| this.form.patchValue({ | this.form.patchValue({ | ||||
| @@ -103,12 +90,11 @@ export class UserTripFormComponent extends AbstractDataFormComponent<UserTripJso | |||||
| this.mediaObjectService.mediaObjectsPost(this.selectedFile).subscribe({ | this.mediaObjectService.mediaObjectsPost(this.selectedFile).subscribe({ | ||||
| next: (mediaObject) => { | next: (mediaObject) => { | ||||
| // 2. Update the form data with the new mediaObject | // 2. Update the form data with the new mediaObject | ||||
| console.log(mediaObject.id); | |||||
| this.form.patchValue({ | this.form.patchValue({ | ||||
| signature: mediaObject | |||||
| signature: mediaObject.id | |||||
| }); | }); | ||||
| console.log('Form after file upload:', this.form.value); | |||||
| // 3. Call the parent method to handle the standard save process | // 3. Call the parent method to handle the standard save process | ||||
| super.onSubmit(); | super.onSubmit(); | ||||
| }, | }, | ||||
| @@ -49,7 +49,9 @@ model/tripJsonld.ts | |||||
| model/tripLocation.ts | model/tripLocation.ts | ||||
| model/tripLocationJsonld.ts | model/tripLocationJsonld.ts | ||||
| model/user.ts | model/user.ts | ||||
| model/userImage.ts | |||||
| model/userJsonld.ts | model/userJsonld.ts | ||||
| model/userJsonldImage.ts | |||||
| model/userTrip.ts | model/userTrip.ts | ||||
| model/userTripEvent.ts | model/userTripEvent.ts | ||||
| model/userTripEventJsonld.ts | model/userTripEventJsonld.ts | ||||
| @@ -9,15 +9,17 @@ | |||||
| * https://openapi-generator.tech | * https://openapi-generator.tech | ||||
| * Do not edit the class manually. | * Do not edit the class manually. | ||||
| */ | */ | ||||
| import { User } from './user'; | |||||
| /** | /** | ||||
| * | * | ||||
| */ | */ | ||||
| export interface MediaObject { | export interface MediaObject { | ||||
| readonly dbId?: number | null; | |||||
| contentUrl?: string | null; | |||||
| readonly filePath?: string | null; | |||||
| readonly createdAt?: string | null; | |||||
| readonly id?: number; | |||||
| file: Blob | null; | |||||
| filePath?: string | null; | |||||
| createdBy?: User; | |||||
| readonly createdAt?: string; | |||||
| } | } | ||||
| @@ -28,7 +28,9 @@ export * from './tripJsonld'; | |||||
| export * from './tripLocation'; | export * from './tripLocation'; | ||||
| export * from './tripLocationJsonld'; | export * from './tripLocationJsonld'; | ||||
| export * from './user'; | export * from './user'; | ||||
| export * from './userImage'; | |||||
| export * from './userJsonld'; | export * from './userJsonld'; | ||||
| export * from './userJsonldImage'; | |||||
| export * from './userTrip'; | export * from './userTrip'; | ||||
| export * from './userTripEvent'; | export * from './userTripEvent'; | ||||
| export * from './userTripEventJsonld'; | export * from './userTripEventJsonld'; | ||||
| @@ -9,6 +9,7 @@ | |||||
| * https://openapi-generator.tech | * https://openapi-generator.tech | ||||
| * Do not edit the class manually. | * Do not edit the class manually. | ||||
| */ | */ | ||||
| import { UserImage } from './userImage'; | |||||
| /** | /** | ||||
| @@ -20,7 +21,7 @@ export interface User { | |||||
| firstName: string; | firstName: string; | ||||
| referenceId: string; | referenceId: string; | ||||
| lastName: string; | lastName: string; | ||||
| image?: string | null; | |||||
| image?: UserImage; | |||||
| readonly imageUrl?: string | null; | readonly imageUrl?: string | null; | ||||
| readonly fullName?: string | null; | readonly fullName?: string | null; | ||||
| /** | /** | ||||
| @@ -0,0 +1,23 @@ | |||||
| /** | |||||
| * Imaq Platform | |||||
| * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) | |||||
| * | |||||
| * The version of the OpenAPI document: 1.0.0 | |||||
| * | |||||
| * | |||||
| * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). | |||||
| * https://openapi-generator.tech | |||||
| * Do not edit the class manually. | |||||
| */ | |||||
| import { User } from './user'; | |||||
| import { MediaObject } from './mediaObject'; | |||||
| export interface UserImage { | |||||
| readonly id?: any | null; | |||||
| file: any | null; | |||||
| filePath?: any | null; | |||||
| createdBy?: User; | |||||
| readonly createdAt?: any | null; | |||||
| } | |||||
| @@ -9,6 +9,7 @@ | |||||
| * https://openapi-generator.tech | * https://openapi-generator.tech | ||||
| * Do not edit the class manually. | * Do not edit the class manually. | ||||
| */ | */ | ||||
| import { UserJsonldImage } from './userJsonldImage'; | |||||
| import { EventJsonldContext } from './eventJsonldContext'; | import { EventJsonldContext } from './eventJsonldContext'; | ||||
| @@ -24,7 +25,7 @@ export interface UserJsonld { | |||||
| firstName: string; | firstName: string; | ||||
| referenceId: string; | referenceId: string; | ||||
| lastName: string; | lastName: string; | ||||
| image?: string | null; | |||||
| image?: UserJsonldImage; | |||||
| readonly imageUrl?: string | null; | readonly imageUrl?: string | null; | ||||
| readonly fullName?: string | null; | readonly fullName?: string | null; | ||||
| /** | /** | ||||
| @@ -0,0 +1,25 @@ | |||||
| /** | |||||
| * Imaq Platform | |||||
| * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) | |||||
| * | |||||
| * The version of the OpenAPI document: 1.0.0 | |||||
| * | |||||
| * | |||||
| * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). | |||||
| * https://openapi-generator.tech | |||||
| * Do not edit the class manually. | |||||
| */ | |||||
| import { MediaObjectJsonld } from './mediaObjectJsonld'; | |||||
| import { EventJsonldContext } from './eventJsonldContext'; | |||||
| export interface UserJsonldImage { | |||||
| context?: EventJsonldContext; | |||||
| readonly id?: any | null; | |||||
| readonly type?: any | null; | |||||
| readonly dbId?: any | null; | |||||
| contentUrl?: any | null; | |||||
| readonly filePath?: any | null; | |||||
| readonly createdAt?: any | null; | |||||
| } | |||||
| @@ -22,7 +22,7 @@ export interface UserTrip { | |||||
| trip: Trip; | trip: Trip; | ||||
| user: User; | user: User; | ||||
| captainName?: string | null; | captainName?: string | null; | ||||
| completed?: boolean; | |||||
| completed: boolean; | |||||
| signature?: MediaObject; | signature?: MediaObject; | ||||
| readonly signatureUrl?: string | null; | readonly signatureUrl?: string | null; | ||||
| completedDate?: string | null; | completedDate?: string | null; | ||||
| @@ -26,7 +26,7 @@ export interface UserTripJsonld { | |||||
| trip: TripJsonld; | trip: TripJsonld; | ||||
| user: UserJsonld; | user: UserJsonld; | ||||
| captainName?: string | null; | captainName?: string | null; | ||||
| completed?: boolean; | |||||
| completed: boolean; | |||||
| signature?: MediaObjectJsonld; | signature?: MediaObjectJsonld; | ||||
| readonly signatureUrl?: string | null; | readonly signatureUrl?: string | null; | ||||
| completedDate?: string | null; | completedDate?: string | null; | ||||
| @@ -57,20 +57,20 @@ class TripApi | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?int $dbId = null; | public ?int $dbId = null; | ||||
| /** | |||||
| * @var VesselApi | |||||
| */ | |||||
| #[ApiProperty( | |||||
| writable: true, | |||||
| readableLink: true, | |||||
| writableLink: true, | |||||
| builtinTypes: [ | |||||
| new Type( | |||||
| 'object', | |||||
| class: VesselApi::class, | |||||
| ) | |||||
| ] | |||||
| )] | |||||
| // /** | |||||
| // * @var VesselApi | |||||
| // */ | |||||
| // #[ApiProperty( | |||||
| // writable: true, | |||||
| // readableLink: true, | |||||
| // writableLink: false, | |||||
| // builtinTypes: [ | |||||
| // new Type( | |||||
| // 'object', | |||||
| // class: VesselApi::class, | |||||
| // ) | |||||
| // ] | |||||
| // )] | |||||
| public ?VesselApi $vessel = null; | public ?VesselApi $vessel = null; | ||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| @@ -80,36 +80,36 @@ class TripApi | |||||
| public ?string $captainName = null; | public ?string $captainName = null; | ||||
| /** | |||||
| * @var LocationApi | |||||
| */ | |||||
| #[ApiProperty( | |||||
| writable: true, | |||||
| readableLink: true, | |||||
| writableLink: true, | |||||
| builtinTypes: [ | |||||
| new Type( | |||||
| 'object', | |||||
| class: LocationApi::class, | |||||
| ) | |||||
| ] | |||||
| )] | |||||
| // /** | |||||
| // * @var LocationApi | |||||
| // */ | |||||
| // #[ApiProperty( | |||||
| // writable: true, | |||||
| // readableLink: true, | |||||
| // writableLink: false, | |||||
| // builtinTypes: [ | |||||
| // new Type( | |||||
| // 'object', | |||||
| // class: LocationApi::class, | |||||
| // ) | |||||
| // ] | |||||
| // )] | |||||
| public ?LocationApi $startLocation = null; | public ?LocationApi $startLocation = null; | ||||
| /** | |||||
| * @var LocationApi | |||||
| */ | |||||
| #[ApiProperty( | |||||
| writable: true, | |||||
| readableLink: true, | |||||
| writableLink: true, | |||||
| builtinTypes: [ | |||||
| new Type( | |||||
| 'object', | |||||
| class: LocationApi::class, | |||||
| ) | |||||
| ] | |||||
| )] | |||||
| // /** | |||||
| // * @var LocationApi | |||||
| // */ | |||||
| // #[ApiProperty( | |||||
| // writable: true, | |||||
| // readableLink: true, | |||||
| // writableLink: false, | |||||
| // builtinTypes: [ | |||||
| // new Type( | |||||
| // 'object', | |||||
| // class: LocationApi::class, | |||||
| // ) | |||||
| // ] | |||||
| // )] | |||||
| public ?LocationApi $endLocation = null; | public ?LocationApi $endLocation = null; | ||||
| #[Assert\NotBlank] | #[Assert\NotBlank] | ||||
| @@ -95,6 +95,7 @@ class UserTripApi | |||||
| public ?string $captainName = null; | public ?string $captainName = null; | ||||
| #[Assert\NotNull] | |||||
| public bool $completed = false; | public bool $completed = false; | ||||
| /** | /** | ||||
| @@ -22,7 +22,6 @@ final class CreateMediaObjectAction extends AbstractController | |||||
| public function __construct( | public function __construct( | ||||
| private MicroMapperInterface $microMapper, | private MicroMapperInterface $microMapper, | ||||
| private EntityManagerInterface $em, | private EntityManagerInterface $em, | ||||
| private SerializerInterface $serializer, | |||||
| private NormalizerInterface $normalizer | private NormalizerInterface $normalizer | ||||
| ) { | ) { | ||||
| } | } | ||||
| @@ -1,67 +1,20 @@ | |||||
| <?php | <?php | ||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 25.01.24 | |||||
| */ | |||||
| namespace App\Entity; | namespace App\Entity; | ||||
| use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | |||||
| use ApiPlatform\Metadata\Delete; | |||||
| use ApiPlatform\Metadata\Get; | |||||
| use ApiPlatform\Metadata\GetCollection; | |||||
| use ApiPlatform\Metadata\Post; | |||||
| use ApiPlatform\OpenApi\Model; | |||||
| use App\Controller\CreateMediaObjectAction; | |||||
| use App\Repository\MediaObjectRepository; | |||||
| use Doctrine\ORM\Mapping as ORM; | use Doctrine\ORM\Mapping as ORM; | ||||
| use Symfony\Component\HttpFoundation\File\File; | use Symfony\Component\HttpFoundation\File\File; | ||||
| use Symfony\Component\Serializer\Annotation\Groups; | |||||
| use Symfony\Component\Validator\Constraints as Assert; | use Symfony\Component\Validator\Constraints as Assert; | ||||
| use Vich\UploaderBundle\Mapping\Annotation as Vich; | use Vich\UploaderBundle\Mapping\Annotation as Vich; | ||||
| #[Vich\Uploadable] | #[Vich\Uploadable] | ||||
| #[ORM\Entity] | |||||
| #[ApiResource( | |||||
| shortName: 'MediaObject', | |||||
| types: ['https://schema.org/MediaObject'], | |||||
| operations: [ | |||||
| new Get(), | |||||
| new GetCollection(), | |||||
| new Post( | |||||
| controller: CreateMediaObjectAction::class, | |||||
| openapi: new Model\Operation( | |||||
| requestBody: new Model\RequestBody( | |||||
| content: new \ArrayObject([ | |||||
| 'multipart/form-data' => [ | |||||
| 'schema' => [ | |||||
| 'type' => 'object', | |||||
| 'properties' => [ | |||||
| 'file' => [ | |||||
| 'type' => 'string', | |||||
| 'format' => 'binary' | |||||
| ] | |||||
| ] | |||||
| ] | |||||
| ] | |||||
| ]) | |||||
| ) | |||||
| ), | |||||
| deserialize: false | |||||
| ), | |||||
| new Delete(), | |||||
| ], | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| )] | |||||
| #[ORM\Entity(repositoryClass: MediaObjectRepository::class)] | |||||
| class MediaObject | class MediaObject | ||||
| { | { | ||||
| #[ORM\Id, ORM\Column, ORM\GeneratedValue] | #[ORM\Id, ORM\Column, ORM\GeneratedValue] | ||||
| private ?int $id = null; | private ?int $id = null; | ||||
| #[ApiProperty(types: ['https://schema.org/contentUrl'])] | |||||
| public ?string $contentUrl = null; | |||||
| #[Vich\UploadableField(mapping: 'media_object', fileNameProperty: 'filePath')] | #[Vich\UploadableField(mapping: 'media_object', fileNameProperty: 'filePath')] | ||||
| #[Assert\NotNull()] | #[Assert\NotNull()] | ||||
| public ?File $file = null; | public ?File $file = null; | ||||
| @@ -87,11 +40,6 @@ class MediaObject | |||||
| return $this->id; | return $this->id; | ||||
| } | } | ||||
| public function getContentUrl(): ?string | |||||
| { | |||||
| return $this->contentUrl; | |||||
| } | |||||
| public function getFile(): ?File | public function getFile(): ?File | ||||
| { | { | ||||
| return $this->file; | return $this->file; | ||||
| @@ -111,5 +59,4 @@ class MediaObject | |||||
| { | { | ||||
| return $this->createdAt; | return $this->createdAt; | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,44 @@ | |||||
| <?php | |||||
| namespace App\Mapper; | |||||
| use App\ApiResource\MediaObjectApi; | |||||
| use App\Entity\MediaObject; | |||||
| use App\Repository\MediaObjectRepository; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | |||||
| use Symfonycasts\MicroMapper\MapperInterface; | |||||
| #[AsMapper(from: MediaObjectApi::class, to: MediaObject::class)] | |||||
| class MediaObjectApiToEntityMapper implements MapperInterface | |||||
| { | |||||
| public function __construct( | |||||
| private MediaObjectRepository $repository | |||||
| ) { | |||||
| } | |||||
| public function load(object $from, string $toClass, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| assert($dto instanceof MediaObjectApi); | |||||
| // Wenn eine ID vorhanden ist, lade das vorhandene Entity | |||||
| if ($dto->id) { | |||||
| $entity = $this->repository->find($dto->id); | |||||
| if (!$entity) { | |||||
| throw new \Exception('MediaObject not found with ID: ' . $dto->id); | |||||
| } | |||||
| return $entity; | |||||
| } | |||||
| // Da neue MediaObjects nur über den Controller erstellt werden, | |||||
| // werfen wir hier eine Exception, wenn kein existierendes MediaObject gefunden wurde | |||||
| throw new \Exception('Cannot create a new MediaObject via API mapping. Use the CreateMediaObjectAction controller instead.'); | |||||
| } | |||||
| public function populate(object $from, object $to, array $context): object | |||||
| { | |||||
| // Da MediaObjects nur über den Controller erstellt/aktualisiert werden sollten, | |||||
| // müssen wir hier nichts tun | |||||
| return $to; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,86 @@ | |||||
| <?php | |||||
| namespace App\Repository; | |||||
| use App\Entity\MediaObject; | |||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| /** | |||||
| * @extends ServiceEntityRepository<MediaObject> | |||||
| * | |||||
| * @method MediaObject|null find($id, $lockMode = null, $lockVersion = null) | |||||
| * @method MediaObject|null findOneBy(array $criteria, array $orderBy = null) | |||||
| * @method MediaObject[] findAll() | |||||
| * @method MediaObject[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | |||||
| */ | |||||
| class MediaObjectRepository extends ServiceEntityRepository | |||||
| { | |||||
| public function __construct(ManagerRegistry $registry) | |||||
| { | |||||
| parent::__construct($registry, MediaObject::class); | |||||
| } | |||||
| /** | |||||
| * Find media objects created by a specific user | |||||
| * | |||||
| * @param int $userId | |||||
| * @return MediaObject[] | |||||
| */ | |||||
| public function findByUser(int $userId): array | |||||
| { | |||||
| return $this->createQueryBuilder('m') | |||||
| ->andWhere('m.createdBy = :userId') | |||||
| ->setParameter('userId', $userId) | |||||
| ->orderBy('m.createdAt', 'DESC') | |||||
| ->getQuery() | |||||
| ->getResult(); | |||||
| } | |||||
| /** | |||||
| * Find recent media objects with limit | |||||
| * | |||||
| * @param int $limit | |||||
| * @return MediaObject[] | |||||
| */ | |||||
| public function findRecent(int $limit = 10): array | |||||
| { | |||||
| return $this->createQueryBuilder('m') | |||||
| ->orderBy('m.createdAt', 'DESC') | |||||
| ->setMaxResults($limit) | |||||
| ->getQuery() | |||||
| ->getResult(); | |||||
| } | |||||
| /** | |||||
| * Save media object to database | |||||
| * | |||||
| * @param MediaObject $mediaObject | |||||
| * @param bool $flush Whether to immediately flush changes | |||||
| * @return void | |||||
| */ | |||||
| public function save(MediaObject $mediaObject, bool $flush = true): void | |||||
| { | |||||
| $this->getEntityManager()->persist($mediaObject); | |||||
| if ($flush) { | |||||
| $this->getEntityManager()->flush(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Remove media object from database | |||||
| * | |||||
| * @param MediaObject $mediaObject | |||||
| * @param bool $flush Whether to immediately flush changes | |||||
| * @return void | |||||
| */ | |||||
| public function remove(MediaObject $mediaObject, bool $flush = true): void | |||||
| { | |||||
| $this->getEntityManager()->remove($mediaObject); | |||||
| if ($flush) { | |||||
| $this->getEntityManager()->flush(); | |||||
| } | |||||
| } | |||||
| } | |||||