From 322c48b78ba731020b9bbf50cbf5d3a32575949f Mon Sep 17 00:00:00 2001 From: lexx Date: Sat, 23 Mar 2024 22:15:06 +0200 Subject: [PATCH] create document flow --- front/js/svelte/records/Edit.svelte | 2 - src/Edge/EdgeCollection.php | 14 +++- src/Edge/EdgeService.php | 13 +++- src/Http/Controller/RecordController.php | 81 ++++++++++------------ src/Record/RecordService.php | 87 +++++++++++++++++++++--- src/Response.php | 14 ++++ src/Revision/Revision.php | 34 ++++++--- src/Revision/RevisionRepo.php | 17 ++--- src/Revision/RevisionService.php | 7 +- src/Schema/Schema/BlockSchema.php | 2 +- src/Schema/Schema/CollectionSchema.php | 2 +- src/Schema/Schema/EdgeSchema.php | 2 +- src/Schema/Schema/FilesSchema.php | 2 +- src/Schema/Schema/Schema.php | 17 ++++- src/Schema/Validator/Validator.php | 65 +++++++----------- 15 files changed, 230 insertions(+), 129 deletions(-) diff --git a/front/js/svelte/records/Edit.svelte b/front/js/svelte/records/Edit.svelte index 0f3cc8b..cc14da6 100644 --- a/front/js/svelte/records/Edit.svelte +++ b/front/js/svelte/records/Edit.svelte @@ -48,7 +48,6 @@ schema: record.schema, status: record.status, _sys: JSON.parse(JSON.stringify(record._sys)), - _file: JSON.parse(JSON.stringify(record._file)), edges: JSON.parse(JSON.stringify(graph.edges)), }; } @@ -82,7 +81,6 @@ schema: record.schema, status: record.status, _sys: record._sys, - _file: record._file, edges: graph.edges, }); } diff --git a/src/Edge/EdgeCollection.php b/src/Edge/EdgeCollection.php index 7a590bb..a57db42 100644 --- a/src/Edge/EdgeCollection.php +++ b/src/Edge/EdgeCollection.php @@ -12,7 +12,8 @@ final class EdgeCollection extends Collection public function __construct( Edge ...$array - ) { + ) + { parent::__construct($array); } @@ -26,9 +27,16 @@ final class EdgeCollection extends Collection public static function fromArray(array $data): EdgeCollection { - $edges = array_map([Edge::class, 'fromArray'], $data); + $edges = collect($data) + ->map([Edge::class, 'fromArray']) + ->unique(fn(Edge $e) => $e->field . $e->source . $e->target); return new EdgeCollection(...$edges); } - + public static function fromJson(string $data): EdgeCollection + { + return self::fromArray(json_decode($data)); + } + + } diff --git a/src/Edge/EdgeService.php b/src/Edge/EdgeService.php index 39483af..b9cebd1 100644 --- a/src/Edge/EdgeService.php +++ b/src/Edge/EdgeService.php @@ -1,6 +1,7 @@ $edges + * @return void + */ + public function update(string $from, Option $edges): void { - $this->edgeRepo->update($from, $edges); + if($edges->isEmpty()){ + return; + } + $this->edgeRepo->update($from, $edges->get()); } public function findAll(): EdgeCollection diff --git a/src/Http/Controller/RecordController.php b/src/Http/Controller/RecordController.php index 28b5f85..4f0a669 100644 --- a/src/Http/Controller/RecordController.php +++ b/src/Http/Controller/RecordController.php @@ -13,11 +13,16 @@ use Lucent\Query\Query; use Lucent\Record\Manager; use Lucent\Record\QueryRecord; use Lucent\Record\RecordService; +use Lucent\Record\Status; use Lucent\Schema\Schema\System; use Lucent\Schema\Validator\ValidatorException; +use Lucent\Support\Result\Result; +use Lucent\Support\Result\Success; use Lucent\Svelte\Svelte; +use PhpOption\Option; use function Lucent\Response\fail; use function Lucent\Response\ok; +use function Lucent\Response\result; class RecordController extends Controller { @@ -59,8 +64,6 @@ class RecordController extends Controller $skip = data_get($urlParams, "skip") ?? 0; $limit = 30; - $records = []; - $graphArray = null; $graph = $this->query ->filter($arguments) ->limit($limit) @@ -72,15 +75,11 @@ class RecordController extends Controller ->parentsDepth(0) ->runWithCount(); - - $records = $graph->getRootRecords()->toArray(); - - $data = [ "schemas" => $this->channelService->channel->schemas, "schema" => $schema, "users" => $users, - "records" => $records, + "records" => $graph->rootRecords, "graph" => toArray($graph), "systemFields" => array_values(System::list()), "operators" => array_values(Operator::list()), @@ -161,15 +160,14 @@ class RecordController extends Controller $schema = $this->channelService->channel->schemas->where("name", $request->input("schema"))->first(); $recordHistory = $this->recordManager->fromSession($request->session())->getRecords(); - $record = $this->recordService->createEmpty($schema, $this->authService->currentUserId()); - $queryRecord = QueryRecord::fromRecord($record); + $record = $this->recordService->createEmpty($schema); return $this->svelte->render( layout: "channel", view: "recordEdit", title: "New Record", data: [ "schema" => $schema, - "record" => $queryRecord, + "record" => $record, "recordHistory" => $recordHistory, "isCreateMode" => true, "isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas()) @@ -222,7 +220,7 @@ class RecordController extends Controller ); } - $record = $graph->records->first(); + $record = $graph->rootRecords->first(); if (!in_array($record->schema, $this->accountService->currentReadableSchemas())) { return $this->svelte->render( @@ -312,42 +310,37 @@ class RecordController extends Controller public function save(Request $request) { $recordId = $request->input("record.id"); - try { - if ($request->input("isCreateMode")) { - $recordId = $this->recordService->create( - schemaName: $request->input("record.schema"), - data: $request->input("record.data"), - id: $recordId ?? "", - file: $request->input("record._file") ?? [], - edges: $request->input("edges") ?? [], - status: $request->input("record.status"), - uploadFromUrl: "" - ); - } else { + $res = match ($request->input("isCreateMode")) { + true => $this->recordService->createDocument( + schemaName: $request->input("record.schema"), + data: $request->input("record.data"), + id: Option::fromValue($recordId), + edges: Option::fromValue($request->input("edges")), + status: Status::from($request->input("record.status")), + ), + default => $this->recordService->update( + id: $request->input("record.id"), + data: $request->input("record.data"), + status: $request->input("record.status"), + edges: $request->input("edges"), + updateEdges: true, + ) + }; - $this->recordService->update( - id: $request->input("record.id"), - data: $request->input("record.data"), - status: $request->input("record.status"), - edges: $request->input("edges"), - updateEdges: true, - ); - } - - $newGraph = $this->query - ->filter(["id" => $recordId]) - ->limit(10) - ->childrenDepth(1) - ->parentsDepth(1) - ->run(); - - } catch (ValidatorException $th) { - return fail($th->getValidatorErrors()); - } catch (LucentException $th) { - return fail($th); + if ($res->error()->isDefined()) { + return result($res); } - return ok(toArray($newGraph)); + + $newGraph = $this->query + ->filter(["id" => $recordId]) + ->limit(10) + ->childrenDepth(1) + ->parentsDepth(1) + ->run(); + + + return result(Success::create(toArray($newGraph))); } diff --git a/src/Record/RecordService.php b/src/Record/RecordService.php index bdd65ef..c884661 100644 --- a/src/Record/RecordService.php +++ b/src/Record/RecordService.php @@ -13,10 +13,17 @@ use Lucent\File\FileService; use Lucent\LucentException; use Lucent\Query\Query; use Lucent\Revision\RevisionService; +use Lucent\Schema\Field\FieldDataInterface; use Lucent\Schema\Field\FieldInterface; use Lucent\Schema\Schema\Schema; use Lucent\Schema\Validator\Validator; +use Lucent\Schema\Validator\ValidatorError; use Lucent\Schema\Validator\ValidatorException; +use Lucent\Support\Collection; +use Lucent\Support\Result\Error; +use Lucent\Support\Result\Result; +use Lucent\Support\Result\Success; +use PhpOption\Option; readonly class RecordService { @@ -34,6 +41,71 @@ readonly class RecordService { } + /** + * @param string $schemaName + * @param array $data + * @param Option $id + * @param Option $edges + * @param Status $status + * @return Result> + */ + public function createDocument( + string $schemaName, + array $data, + Option $id, + Option $edges, + Status $status = Status::DRAFT, + ): Result + { + $schema = $this->channelService->getSchema($schemaName)->get(); + + $formattedData = $this->inputFormatter->fill($schemaName, new RecordData($data)); + $newRecordId = $id->getOrElse(Id::new()); + $uniqueEdges = $this->getUniqueEdges($edges, $newRecordId, $schemaName); + + $record = new Document( + id: $newRecordId, + schema: $schema->name, + status: $status, + _sys: System::newRecord($this->authService->currentUserId()), + data: $formattedData, + ); + + if ($status === Status::PUBLISHED) { + $errors = $this->recordValidator->check($schemaName, $record->data); + if ($errors->isNotEmpty()) { + return Error::create($errors); + } + } + + RecordRepo::create($record); + $this->edgeService->update($record->id, $uniqueEdges); + $this->revisionService->create($record, $uniqueEdges); + return Success::create($record->id); + } + + /** + * @param Option $edges + * @param string $recordId + * @param string $schemaName + * @return Option + */ + private function getUniqueEdges(Option $edges, string $recordId, string $schemaName): Option + { + if($edges->isEmpty()){ + return none(); + } + + $uniqueEdges = collect($edges) + ->map(function ($edge, $index) use ($recordId, $schemaName) { + $edge['source'] = $recordId; + $edge['sourceSchema'] = $schemaName; + $edge["rank"] = $index; + return $edge; + }); + return some(EdgeCollection::fromArray($uniqueEdges->toArray())); + } + /** * @throws LucentException * @throws ValidatorException @@ -102,7 +174,8 @@ readonly class RecordService * @throws LucentException * @throws ValidatorException */ - public function update( + public + function update( string $id, array $data, string $status = "draft", @@ -120,7 +193,7 @@ readonly class RecordService $uniqueEdgesCollection = new EdgeCollection(); if ($updateEdges) { $uniqueEdges = collect($edges) - ->map(function ($edge, $index):Edge { + ->map(function ($edge, $index): Edge { $edge["rank"] = $index; return Edge::fromArray($edge); }) @@ -162,7 +235,8 @@ readonly class RecordService /** */ - public function changeStatusBulk( + public + function changeStatusBulk( string $status, array $recordsIds, ): void @@ -253,21 +327,18 @@ readonly class RecordService Schema $schema, ): Record { - - $defaultValues = $schema->fields->reduce(function ($carry, FieldInterface $f) { + $defaultValues = $schema->getDataFields()->reduce(function ($carry, FieldDataInterface $f) { $carry[$f->name] = $f->default ?? null; return $carry; }, []); $formattedData = $this->inputFormatter->fill($schema->name, new RecordData($defaultValues)); - - return new Record( + return new Document( id: Id::new(), schema: $schema->name, status: Status::DRAFT, _sys: System::newRecord($this->authService->currentUserId()), data: $formattedData, - _file: null, ); } diff --git a/src/Response.php b/src/Response.php index 43c5cca..594f95f 100644 --- a/src/Response.php +++ b/src/Response.php @@ -5,8 +5,22 @@ namespace Lucent\Response; use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; use Lucent\LucentException; +use Lucent\Support\Result\Error; +use Lucent\Support\Result\Result; +use Lucent\Support\Result\Success; use Throwable; + +function result(Result $result, ?int $successCode = null, ?int $errorCode = null): Response +{ + return match (get_class($result)) { + Success::class => response($result->success()->get(), $successCode ?? 200), + Error::class => response([ + "error" => $result->error()->get() + ], $errorCode ?? 400) + }; +} + function fail(Throwable|string|array $error, int $code = 400): Response { $message = "Something went wrong"; diff --git a/src/Revision/Revision.php b/src/Revision/Revision.php index 2f248a9..9d9947c 100644 --- a/src/Revision/Revision.php +++ b/src/Revision/Revision.php @@ -8,25 +8,39 @@ use Lucent\Record\FileInfo; use Lucent\Record\Record; use Lucent\Record\RecordData; use Lucent\Record\System; +use PhpOption\Option; readonly class Revision { + /** + * @param string $id + * @param string $recordId + * @param string $schema + * @param System $_sys + * @param RecordData $data + * @param Option $_edges + * @param Option $_file + */ function __construct( - public string $id, - public string $recordId, - public string $schema, - public System $_sys, - public RecordData $data, - public EdgeCollection $_edges, - public ?FileInfo $_file = null, + public string $id, + public string $recordId, + public string $schema, + public System $_sys, + public RecordData $data, + public Option $_edges, + public Option $_file, ) { } - - public static function fromRecord(Record $record, EdgeCollection $edges): Revision + /** + * @param Record $record + * @param Option $edges + * @return Revision + */ + public static function fromRecord(Record $record, Option $edges): Revision { return new Revision( id: (string)Str::uuid(), @@ -35,7 +49,7 @@ readonly class Revision _sys: $record->_sys, data: $record->data, _edges: $edges, - _file: $record->_file + _file: empty($record->_file) ? none() : some($record->_file) ); } diff --git a/src/Revision/RevisionRepo.php b/src/Revision/RevisionRepo.php index 2fad5d0..163feb4 100644 --- a/src/Revision/RevisionRepo.php +++ b/src/Revision/RevisionRepo.php @@ -3,7 +3,6 @@ namespace Lucent\Revision; use Illuminate\Support\Facades\DB; -use Lucent\Edge\Edge; use Lucent\Edge\EdgeCollection; use Lucent\Record\FileInfo; use Lucent\Record\RecordData; @@ -80,23 +79,15 @@ class RevisionRepo "recordId" => $revision->recordId, "schema" => $revision->schema, "_sys" => json_encode($revision->_sys), - "_file" => json_encode($revision->_file), + "_file" => $revision->_file->map(fn($v) => json_encode($v))->getOrElse(null), "data" => json_encode($revision->data), - "_edges" => $revision->_edges->toJson(), + "_edges" => $revision->_edges->getOrElse(null)->toJson(), ]; } public function fromDB(stdClass $data): Revision { - $file = json_decode($data->_file, true); - if (!empty($file)) { - $file = new FileInfo(...$file); - } else { - $file = null; - } - - $edges = array_map(fn($e) => Edge::fromArray($e), json_decode($data->_edges ?? "[]", true)); return new Revision( id: $data->id, @@ -104,8 +95,8 @@ class RevisionRepo schema: $data->schema, _sys: System::fromArray(json_decode($data->_sys, true)), data: new RecordData(json_decode($data->data, true)), - _edges: new EdgeCollection(...$edges), - _file: $file + _edges: Option::fromValue($data->_edges)->map([EdgeCollection::class,'fromJson']), + _file: Option::fromValue($data->_file)->map([FileInfo::class,'fromJSON']) ); } } diff --git a/src/Revision/RevisionService.php b/src/Revision/RevisionService.php index 4125e08..df4997d 100644 --- a/src/Revision/RevisionService.php +++ b/src/Revision/RevisionService.php @@ -37,7 +37,12 @@ readonly class RevisionService } - public function create(Record $record, EdgeCollection $edges): void + /** + * @param Record $record + * @param Option $edges + * @return void + */ + public function create(Record $record, Option $edges): void { $schema = $this->channelService->getSchema($record->schema)->get(); if($schema->revisions <= 0){ diff --git a/src/Schema/Schema/BlockSchema.php b/src/Schema/Schema/BlockSchema.php index 7614dae..f7a7de3 100644 --- a/src/Schema/Schema/BlockSchema.php +++ b/src/Schema/Schema/BlockSchema.php @@ -5,7 +5,7 @@ namespace Lucent\Schema\Schema; use Lucent\Schema\Field\FieldInterface; use Lucent\Support\Collection; -class BlockSchema implements Schema +class BlockSchema extends Schema { public Type $type = Type::BLOCK; diff --git a/src/Schema/Schema/CollectionSchema.php b/src/Schema/Schema/CollectionSchema.php index ca540b4..3d4a298 100644 --- a/src/Schema/Schema/CollectionSchema.php +++ b/src/Schema/Schema/CollectionSchema.php @@ -5,7 +5,7 @@ namespace Lucent\Schema\Schema; use Lucent\Schema\Field\FieldInterface; use Lucent\Support\Collection; -class CollectionSchema implements Schema +class CollectionSchema extends Schema { public Type $type = Type::COLLECTION; diff --git a/src/Schema/Schema/EdgeSchema.php b/src/Schema/Schema/EdgeSchema.php index c92e385..e307512 100644 --- a/src/Schema/Schema/EdgeSchema.php +++ b/src/Schema/Schema/EdgeSchema.php @@ -5,7 +5,7 @@ namespace Lucent\Schema\Schema; use Lucent\Schema\Field\FieldDataInterface; use Lucent\Support\Collection; -class EdgeSchema implements Schema +class EdgeSchema extends Schema { public Type $type = Type::EDGE; diff --git a/src/Schema/Schema/FilesSchema.php b/src/Schema/Schema/FilesSchema.php index e8d7ac3..f0fb47d 100644 --- a/src/Schema/Schema/FilesSchema.php +++ b/src/Schema/Schema/FilesSchema.php @@ -5,7 +5,7 @@ namespace Lucent\Schema\Schema; use Lucent\Schema\Field\FieldInterface; use Lucent\Support\Collection; -class FilesSchema implements Schema +class FilesSchema extends Schema { public Type $type = Type::FILES; diff --git a/src/Schema/Schema/Schema.php b/src/Schema/Schema/Schema.php index 2140a4e..fb05885 100644 --- a/src/Schema/Schema/Schema.php +++ b/src/Schema/Schema/Schema.php @@ -2,8 +2,21 @@ namespace Lucent\Schema\Schema; -interface Schema +use Lucent\Schema\Field\FieldDataInterface; +use Lucent\Schema\Field\FieldInterface; +use Lucent\Support\Collection; + +abstract class Schema { - + /** + * @var Collection + */ + public Collection $fields; + /** + * @return Collection + */ + public function getDataFields(): Collection{ + return $this->fields->filter(fn(FieldInterface $f) => $f instanceof FieldDataInterface)->values(); + } } diff --git a/src/Schema/Validator/Validator.php b/src/Schema/Validator/Validator.php index 8c3ad80..508c8ff 100644 --- a/src/Schema/Validator/Validator.php +++ b/src/Schema/Validator/Validator.php @@ -3,10 +3,10 @@ namespace Lucent\Schema\Validator; use Lucent\Channel\ChannelService; -use Lucent\Edge\EdgeCollection; use Lucent\Record\RecordData; -use Lucent\Schema\Field\FieldInterface; +use Lucent\Schema\Field\FieldDataInterface; use Lucent\Support\Collection; +use PhpOption\Option; class Validator @@ -23,55 +23,40 @@ class Validator * @return Collection */ public function check( - string $schemaName, - RecordData $data, - ?EdgeCollection $edges, + string $schemaName, + RecordData $data, ): Collection { $schema = $this->channelService->getSchema($schemaName)->get(); - return $schema->fields - ->map(fn(FieldInterface $f) => $this->validate($f, $data, $edges)) - ->filter(fn(?ValidatorError $error) => !empty($error)) + return $schema->getDataFields() + ->map(fn(FieldDataInterface $f) => $this->validate($f, $data)) + ->filter(fn(Option $error) => $error->isDefined()) ->values(); } - public function validate(FieldInterface $field, RecordData $recordData, ?EdgeCollection $edges): ?ValidatorError + /** + * @param FieldDataInterface $field + * @param RecordData $recordData + * @return Option + */ + public function validate(FieldDataInterface $field, RecordData $recordData): Option { $value = $recordData->get($field->name); - if ($field instanceof RequiredInterface && $field->required && $field->failRequired($value)) { - return new ValidatorError($field->name, $field->label, "Field is required"); - } - if ($field instanceof MinMaxInterface && !is_null($field->min) && $field->failMin($value)) { - return new ValidatorError($field->name, $field->label, "The minimum {$field->label} can be {$field->min}"); - } - - if ($field instanceof MinMaxInterface && !is_null($field->max) && $field->failMax($value)) { - return new ValidatorError($field->name, $field->label, "The maximum {$field->label} can be {$field->min}"); - } - - return null; - } - - /** - * @param Collection $errors - * @throws ValidatorException - */ - public function throwException(Collection $errors): void - { - throw new ValidatorException($this->errorsByField($errors)); - } - - /** - * @param Collection $errors - * @return array - **/ - public function errorsByField(Collection $errors): array - { - - return collect($errors)->keyBy("fieldName")->toArray(); + return match (true) { + $field instanceof RequiredInterface + && $field->required + && $field->failRequired($value) => some(new ValidatorError($field->name, $field->label, "Field is required")), + $field instanceof MinMaxInterface + && !is_null($field->min) + && $field->failMin($value) => some(new ValidatorError($field->name, $field->label, "The minimum {$field->label} can be {$field->min}")), + $field instanceof MinMaxInterface + && !is_null($field->max) + && $field->failMax($value) => some(new ValidatorError($field->name, $field->label, "The maximum {$field->label} can be {$field->min}")), + default => none() + }; }