create document flow

This commit is contained in:
2024-03-23 22:15:06 +02:00
parent c649077e37
commit 322c48b78b
15 changed files with 230 additions and 129 deletions
-2
View File
@@ -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,
});
}
+11 -3
View File
@@ -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));
}
}
+11 -2
View File
@@ -1,6 +1,7 @@
<?php namespace Lucent\Edge;
use Lucent\LucentException;
use PhpOption\Option;
class EdgeService
{
@@ -35,9 +36,17 @@ class EdgeService
return $edge;
}
public function update(string $from, EdgeCollection $edges): void
/**
* @param string $from
* @param Option<EdgeCollection> $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
+37 -44
View File
@@ -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)));
}
+79 -8
View File
@@ -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<string> $id
* @param Option<array> $edges
* @param Status $status
* @return Result<string|Collection<ValidatorError>>
*/
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<array> $edges
* @param string $recordId
* @param string $schemaName
* @return Option<EdgeCollection>
*/
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,
);
}
+14
View File
@@ -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";
+24 -10
View File
@@ -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<EdgeCollection> $_edges
* @param Option<FileInfo> $_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<EdgeCollection> $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)
);
}
+4 -13
View File
@@ -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'])
);
}
}
+6 -1
View File
@@ -37,7 +37,12 @@ readonly class RevisionService
}
public function create(Record $record, EdgeCollection $edges): void
/**
* @param Record $record
* @param Option<EdgeCollection> $edges
* @return void
*/
public function create(Record $record, Option $edges): void
{
$schema = $this->channelService->getSchema($record->schema)->get();
if($schema->revisions <= 0){
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+15 -2
View File
@@ -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<FieldInterface>
*/
public Collection $fields;
/**
* @return Collection<FieldDataInterface>
*/
public function getDataFields(): Collection{
return $this->fields->filter(fn(FieldInterface $f) => $f instanceof FieldDataInterface)->values();
}
}
+25 -40
View File
@@ -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<ValidatorError>
*/
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<ValidatorError>
*/
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<ValidatorError> $errors
* @throws ValidatorException
*/
public function throwException(Collection $errors): void
{
throw new ValidatorException($this->errorsByField($errors));
}
/**
* @param Collection<ValidatorError> $errors
* @return array<string,ValidatorError>
**/
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()
};
}