ソースを参照

wip not working

master
Daniel 1年前
コミット
b006d28af0
20個のファイルの変更302行の追加180行の削除
  1. +1
    -1
      angular/openapi.json
  2. +21
    -21
      angular/openapi.yaml
  3. +5
    -4
      angular/src/app/_forms/apiForms.ts
  4. +33
    -31
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.html
  5. +3
    -17
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts
  6. +2
    -0
      angular/src/app/core/api/v1/.openapi-generator/FILES
  7. +6
    -4
      angular/src/app/core/api/v1/model/mediaObject.ts
  8. +2
    -0
      angular/src/app/core/api/v1/model/models.ts
  9. +2
    -1
      angular/src/app/core/api/v1/model/user.ts
  10. +23
    -0
      angular/src/app/core/api/v1/model/userImage.ts
  11. +2
    -1
      angular/src/app/core/api/v1/model/userJsonld.ts
  12. +25
    -0
      angular/src/app/core/api/v1/model/userJsonldImage.ts
  13. +1
    -1
      angular/src/app/core/api/v1/model/userTrip.ts
  14. +1
    -1
      angular/src/app/core/api/v1/model/userTripJsonld.ts
  15. +42
    -42
      httpdocs/src/ApiResource/TripApi.php
  16. +1
    -0
      httpdocs/src/ApiResource/UserTripApi.php
  17. +0
    -1
      httpdocs/src/Controller/CreateMediaObjectAction.php
  18. +2
    -55
      httpdocs/src/Entity/MediaObject.php
  19. +44
    -0
      httpdocs/src/Mapper/MediaObjectApiToEntityMapper.php
  20. +86
    -0
      httpdocs/src/Repository/MediaObjectRepository.php

+ 1
- 1
angular/openapi.json
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 21
- 21
angular/openapi.yaml ファイルの表示

@@ -2317,28 +2317,26 @@ components:
description: ''
deprecated: false
properties:
dbId:
id:
readOnly: true
type:
- integer
- 'null'
contentUrl:
externalDocs:
url: 'https://schema.org/contentUrl'
type: integer
file:
type:
- string
- 'null'
format: binary
filePath:
readOnly: true
type:
- string
- 'null'
createdBy:
$ref: '#/components/schemas/User'
createdAt:
readOnly: true
type:
- string
- 'null'
type: string
format: date-time
required:
- file
MediaObject.jsonld:
type: object
description: ''
@@ -2676,11 +2674,11 @@ components:
lastName:
type: string
image:
type:
- string
- 'null'
format: iri-reference
example: 'https://example.com/'
anyOf:
-
$ref: '#/components/schemas/MediaObject'
-
type: 'null'
imageUrl:
readOnly: true
type:
@@ -2758,11 +2756,11 @@ components:
lastName:
type: string
image:
type:
- string
- 'null'
format: iri-reference
example: 'https://example.com/'
anyOf:
-
$ref: '#/components/schemas/MediaObject.jsonld'
-
type: 'null'
imageUrl:
readOnly: true
type:
@@ -2836,6 +2834,7 @@ components:
required:
- trip
- user
- completed
UserTrip.jsonld:
type: object
description: ''
@@ -2900,6 +2899,7 @@ components:
required:
- trip
- user
- completed
UserTripEvent:
type: object
description: ''


+ 5
- 4
angular/src/app/_forms/apiForms.ts ファイルの表示

@@ -32,9 +32,10 @@ export const locationJsonldForm = 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, []),
createdBy: new FormControl(null, []),
createdAt: new FormControl(null, [])
});

@@ -140,7 +141,7 @@ export const userTripForm = new FormGroup({
trip: new FormControl(null, [Validators.required]),
user: new FormControl(null, [Validators.required]),
captainName: new FormControl(null, []),
completed: new FormControl(null, []),
completed: new FormControl(null, [Validators.required]),
signature: new FormControl(null, []),
signatureUrl: new FormControl(null, []),
completedDate: new FormControl(null, []),
@@ -152,7 +153,7 @@ export const userTripJsonldForm = new FormGroup({
trip: new FormControl(null, [Validators.required]),
user: new FormControl(null, [Validators.required]),
captainName: new FormControl(null, []),
completed: new FormControl(null, []),
completed: new FormControl(null, [Validators.required]),
signature: new FormControl(null, []),
signatureUrl: new FormControl(null, []),
completedDate: new FormControl(null, []),


+ 33
- 31
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.html ファイルの表示

@@ -1,41 +1,43 @@
<div class="spt-container">
<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 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 }}
</button>



+ 3
- 17
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts ファイルの表示

@@ -5,7 +5,7 @@ import {
FormSubmitEvent
} from "@app/_components/_abstract/abstract-data-form-component";
import {
LocationService, MediaObjectJsonld, MediaObjectService, TripJsonld,
LocationService, MediaObjectJsonld, MediaObjectService, TripJsonld, UserTrip,
UserTripJsonld,
UserTripService,
VesselService
@@ -74,21 +74,8 @@ export class UserTripFormComponent extends AbstractDataFormComponent<UserTripJso
}

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) {
// 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;
if (currentValue === null) {
this.form.patchValue({
@@ -103,12 +90,11 @@ export class UserTripFormComponent extends AbstractDataFormComponent<UserTripJso
this.mediaObjectService.mediaObjectsPost(this.selectedFile).subscribe({
next: (mediaObject) => {
// 2. Update the form data with the new mediaObject
console.log(mediaObject.id);
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
super.onSubmit();
},


+ 2
- 0
angular/src/app/core/api/v1/.openapi-generator/FILES ファイルの表示

@@ -49,7 +49,9 @@ model/tripJsonld.ts
model/tripLocation.ts
model/tripLocationJsonld.ts
model/user.ts
model/userImage.ts
model/userJsonld.ts
model/userJsonldImage.ts
model/userTrip.ts
model/userTripEvent.ts
model/userTripEventJsonld.ts


+ 6
- 4
angular/src/app/core/api/v1/model/mediaObject.ts ファイルの表示

@@ -9,15 +9,17 @@
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { User } from './user';


/**
*
*/
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;
}


+ 2
- 0
angular/src/app/core/api/v1/model/models.ts ファイルの表示

@@ -28,7 +28,9 @@ export * from './tripJsonld';
export * from './tripLocation';
export * from './tripLocationJsonld';
export * from './user';
export * from './userImage';
export * from './userJsonld';
export * from './userJsonldImage';
export * from './userTrip';
export * from './userTripEvent';
export * from './userTripEventJsonld';


+ 2
- 1
angular/src/app/core/api/v1/model/user.ts ファイルの表示

@@ -9,6 +9,7 @@
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { UserImage } from './userImage';


/**
@@ -20,7 +21,7 @@ export interface User {
firstName: string;
referenceId: string;
lastName: string;
image?: string | null;
image?: UserImage;
readonly imageUrl?: string | null;
readonly fullName?: string | null;
/**


+ 23
- 0
angular/src/app/core/api/v1/model/userImage.ts ファイルの表示

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


+ 2
- 1
angular/src/app/core/api/v1/model/userJsonld.ts ファイルの表示

@@ -9,6 +9,7 @@
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { UserJsonldImage } from './userJsonldImage';
import { EventJsonldContext } from './eventJsonldContext';


@@ -24,7 +25,7 @@ export interface UserJsonld {
firstName: string;
referenceId: string;
lastName: string;
image?: string | null;
image?: UserJsonldImage;
readonly imageUrl?: string | null;
readonly fullName?: string | null;
/**


+ 25
- 0
angular/src/app/core/api/v1/model/userJsonldImage.ts ファイルの表示

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


+ 1
- 1
angular/src/app/core/api/v1/model/userTrip.ts ファイルの表示

@@ -22,7 +22,7 @@ export interface UserTrip {
trip: Trip;
user: User;
captainName?: string | null;
completed?: boolean;
completed: boolean;
signature?: MediaObject;
readonly signatureUrl?: string | null;
completedDate?: string | null;


+ 1
- 1
angular/src/app/core/api/v1/model/userTripJsonld.ts ファイルの表示

@@ -26,7 +26,7 @@ export interface UserTripJsonld {
trip: TripJsonld;
user: UserJsonld;
captainName?: string | null;
completed?: boolean;
completed: boolean;
signature?: MediaObjectJsonld;
readonly signatureUrl?: string | null;
completedDate?: string | null;


+ 42
- 42
httpdocs/src/ApiResource/TripApi.php ファイルの表示

@@ -57,20 +57,20 @@ class TripApi
#[ApiProperty(writable: false)]
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;

#[ApiProperty(writable: false)]
@@ -80,36 +80,36 @@ class TripApi

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;

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

#[Assert\NotBlank]


+ 1
- 0
httpdocs/src/ApiResource/UserTripApi.php ファイルの表示

@@ -95,6 +95,7 @@ class UserTripApi

public ?string $captainName = null;

#[Assert\NotNull]
public bool $completed = false;

/**


+ 0
- 1
httpdocs/src/Controller/CreateMediaObjectAction.php ファイルの表示

@@ -22,7 +22,6 @@ final class CreateMediaObjectAction extends AbstractController
public function __construct(
private MicroMapperInterface $microMapper,
private EntityManagerInterface $em,
private SerializerInterface $serializer,
private NormalizerInterface $normalizer
) {
}


+ 2
- 55
httpdocs/src/Entity/MediaObject.php ファイルの表示

@@ -1,67 +1,20 @@
<?php
/**
* @author Daniel Knudsen <d.knudsen@spawntree.de>
* @date 25.01.24
*/


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 Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

#[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
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;

#[ApiProperty(types: ['https://schema.org/contentUrl'])]
public ?string $contentUrl = null;

#[Vich\UploadableField(mapping: 'media_object', fileNameProperty: 'filePath')]
#[Assert\NotNull()]
public ?File $file = null;
@@ -87,11 +40,6 @@ class MediaObject
return $this->id;
}

public function getContentUrl(): ?string
{
return $this->contentUrl;
}

public function getFile(): ?File
{
return $this->file;
@@ -111,5 +59,4 @@ class MediaObject
{
return $this->createdAt;
}

}

+ 44
- 0
httpdocs/src/Mapper/MediaObjectApiToEntityMapper.php ファイルの表示

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

+ 86
- 0
httpdocs/src/Repository/MediaObjectRepository.php ファイルの表示

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

読み込み中…
キャンセル
保存