This commit is contained in:
2023-10-02 23:10:49 +03:00
commit c6cb488379
255 changed files with 18731 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Lucent\Record;
class File
{
function __construct(
public readonly string $originalName,
public readonly string $mime,
public readonly string $path,
public readonly int $size,
public readonly int $width,
public readonly int $height,
public readonly string $checksum,
)
{
}
public static function fromArray(array $data): File
{
return new File(
originalName: data_get($data, "originalName"),
mime: data_get($data, "mime"),
path: data_get($data, "path"),
size: data_get($data, "size"),
width: data_get($data, "width"),
height: data_get($data, "height"),
checksum: data_get($data, "checksum"),
);
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Record;
use Lucent\Channel\ChannelService;
use Lucent\Schema\FieldInterface;
class InputFormatter
{
public function __construct(
public ChannelService $channelService,
)
{
}
public function fill(string $schemaName, RecordData $input): RecordData
{
$schema = $this->channelService->getSchema($schemaName)->get();
$data = $schema->fields->reduce(fn(array $carry, FieldInterface $field) => $field->format($input->toArray(), $carry), []);
return new RecordData($data);
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
namespace Lucent\Record;
use Illuminate\Contracts\Session\Session;
use Lucent\Primitive\Collection;
use Lucent\Query\Query;
use Lucent\Schema\Schema;
class Manager
{
public array $records = [];
public Session $session;
public function fromSession(Session $session): Manager
{
$this->session = $session;
$this->records = $session->get("manager") ?? [];
return $this;
}
public function __construct(
private readonly Query $query
)
{
}
/**
* @param string[] $records
*/
public function addRecords(array $records): Manager
{
$this->records = $records;
return $this;
}
public function push(string $recordId): Manager
{
$records = $this->getIdsExcept($recordId);
$records[] = $recordId;
$records = array_unique($records);
$records = array_values($records);
$records = array_slice($records, -5);
$records = array_values($records);
$this->records = $records;
$this->save();
return $this;
}
private function save(): void
{
$this->session->put("manager", $this->records);
}
public function getIdsExcept(?string $id): array
{
return collect($this->records)->filter(fn($arec) => $arec !== $id)->values()->toArray();
}
/**
* @param QueryRecord[] $records
* @return QueryRecord[] $records
*/
public function order(array $records): array
{
$recordsById = collect($records)->keyBy("id")->toArray();
return collect($this->records)->reverse()->values()->reduce(function ($carry, $arecId) use ($recordsById) {
if (isset($recordsById[$arecId])) {
$carry[] = $recordsById[$arecId];
}
return $carry;
}, []);
}
/**
* @param Collection<Schema> $schemas
*/
public function getRecords(?string $ignoreId = null): array
{
$queryResult = $this->query
->filter(["id_in" => $this->getIdsExcept($ignoreId)])
->limit(7)
->run();
$graph = $queryResult->getQueryRecords();
return $this->order($graph->records->toArray());
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace Lucent\Record;
use Lucent\LucentException;
class QueryRecord
{
function __construct(
public string $id,
public System $_sys,
public RecordData $data,
public bool $isRoot,
public ?File $_file = null,
public array $_children = [],
)
{
}
public function toArray(): array
{
return json_decode(json_encode($this), true);
}
/**
* @throws LucentException
*/
public static function fromArray(array $data): QueryRecord
{
return new QueryRecord(
id: $data["id"],
_sys: System::fromArray($data["_sys"]),
data: new RecordData($data["data"]),
isRoot: $data["isRoot"] ?? false,
_file: $data["_file"] ? new File(...$data["_file"]) : null,
);
}
public static function fromRecord(Record $record): QueryRecord
{
return new QueryRecord(
id: $record->id,
_sys: $record->_sys,
data: $record->data,
isRoot: false,
_file: $record->_file,
);
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace Lucent\Record;
use JsonSerializable;
use stdClass;
class Record implements JsonSerializable
{
function __construct(
public string $id,
public System $_sys,
public RecordData $data,
public ?File $_file = null,
)
{
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
public function toDB(): array
{
return [
"id" => $this->id,
"_sys" => json_encode($this->_sys),
"_file" => json_encode($this->_file),
"data" => json_encode($this->data),
];
}
public static function fromDB(stdClass $data): Record
{
$file = json_decode($data->_file, true);
if (!empty($file)) {
$file = new File(...$file);
} else {
$file = null;
}
return new Record(
id: $data->id,
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_file: $file,
);
}
public function jsonSerialize(): static
{
return $this;
}
public static function fromArray(array $data): Record
{
$file = null;
if (!empty($data["_file"])) {
$file = File::fromArray($data["_file"]);
}
return new Record(
id: $data["id"],
_sys: System::fromArray($data["_sys"]),
data: new RecordData($data["data"]),
_file: $file,
);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Lucent\Record;
use Lucent\ArrayContainer;
class RecordData extends ArrayContainer
{
public function merge(RecordData $data): RecordData
{
$this->data = array_merge($this->data, $data->toArray());
return $this;
}
public function toArray(): array
{
return $this->data;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Lucent\Record;
use Lucent\Edge\QueryEdge;
class RecordGraph
{
/**
* @param \Lucent\Edge\QueryEdge[] $edges
* @param \Lucent\Record\EdgeRecord[] $nodes
*/
function __construct(
public readonly array $edges,
public readonly array $nodes,
) {
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
public static function fromArray(array $data): RecordGraph
{
return new RecordGraph(
edges: collect($data["edges"] ?? [])->map(fn ($edge) => new QueryEdge(...$edge))->toArray(),
nodes: collect($data["nodes"] ?? [])->map(fn ($node) => EdgeRecord::fromArray($node))->toArray(),
);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Lucent\Record;
use Illuminate\Support\Facades\DB;
class RecordRepo
{
public static function create(Record $record): void
{
$recordToDB = $record->toDB();
DB::table("records")->insert($recordToDB);
}
/**
* @param array<string> $ids
*/
public static function updateStatusBulk(RecordStatus $status, array $ids): void
{
DB::table("records")->whereIn("id", $ids)->update([
'_sys->status' => $status->value()
]);
}
public static function update(Record $record): void
{
$recordToDB = $record->toDB();
DB::table("records")->where("id", $record->id)->update($recordToDB);
}
/**
* @param string[] $ids
*/
public static function deleteMany(
array $ids,
): void
{
DB::table("records")
->whereIn("id", $ids)->delete();
DB::table("edges")->whereIn("source", $ids)->delete();
DB::table("edges")->whereIn("target", $ids)->delete();
DB::table("revisions")->whereIn("recordId", $ids)->delete();
}
}
+263
View File
@@ -0,0 +1,263 @@
<?php
namespace Lucent\Record;
use Illuminate\Support\Str;
use Lucent\Account\AuthService;
use Lucent\Channel\ChannelService;
use Lucent\Edge\Edge;
use Lucent\Edge\EdgeCollection;
use Lucent\Edge\EdgeRepo;
use Lucent\File\FileService;
use Lucent\Id\Id;
use Lucent\LucentException;
use Lucent\Query\Query;
use Lucent\Revision\RevisionService;
use Lucent\Schema\FieldInterface;
use Lucent\Schema\Schema;
use Lucent\Schema\Validator\Validator;
use Lucent\Schema\Validator\ValidatorException;
readonly class RecordService
{
public function __construct(
private AuthService $authService,
private RevisionService $revisionService,
private ChannelService $channelService,
private Validator $recordValidator,
private Query $query,
private InputFormatter $inputFormatter
)
{
}
/**
* @throws LucentException
* @throws ValidatorException
*/
public
function create(
string $schemaName,
array $data,
?string $id = null,
array $file = [],
array $edges = [],
string $status = "draft",
string $uploadFromUrl = "",
): string
{
$schema = $this->channelService->channel->schemas->where("name", $schemaName)->first();
if (empty($schema)) {
throw new LucentException("The schema " . $schemaName . " does not exist");
}
$formattedData = $this->inputFormatter->fill($schemaName, new RecordData($data));
if (empty($formattedData["id"])) {
$formattedData["id"] = Id::new();
}
$uploadResult = FileService::create($schema, $uploadFromUrl, $file);
$uniqueEdges = collect($edges)
->map(fn($e) => (array)(new Edge(...$e)))
->map(function ($edge, $index) {
$edgeData = (array)(new Edge(...$edge));
$edgeData["rank"] = $index;
return $edgeData;
})
->unique(fn($e) => $e['field'] . $e['source'] . $e['target'] . $e['sourceSchema'])
->values()->toArray();
$uniqueEdgesCollection = EdgeCollection::fromArray($uniqueEdges);
if ($uploadResult->isDuplicate) {
EdgeRepo::update($uploadResult->duplicateId, $uniqueEdgesCollection);
return $uploadResult->duplicateId;
}
$record = new Record(
id: $id ?? Id::new(),
_sys: System::newRecord($schema, $this->authService->currentUserId(), $status),
data: $formattedData,
_file: $uploadResult->recordFile,
);
$errors = $this->recordValidator->check($schemaName, $record->data, $uniqueEdgesCollection);
if ($errors->isNotEmpty()) {
$this->recordValidator->throwException($errors);
}
RecordRepo::create($record);
EdgeRepo::update($record->id, $uniqueEdgesCollection);
$this->revisionService->create($record);
return $record->id;
}
/**
* @throws LucentException
* @throws ValidatorException
*/
public
function update(
string $id,
array $data,
string $status = "draft",
array $edges = [],
bool $updateEdges = false
): void
{
$queryResult = $this->query->filter(["id" => $id])->run();
$record = $queryResult->getQueryRecords()->records[0] ?? null;
if (empty($record)) {
throw new LucentException("Record id is missing");
}
$formattedData = $this->inputFormatter->fill($record->_sys->schema, new RecordData($data));
if ($updateEdges) {
$uniqueEdges = collect($edges)
->map(function ($edge, $index) {
$edgeData = (array)(new Edge(...$edge));
$edgeData["rank"] = $index;
return $edgeData;
})
->unique(fn($e) => $e['field'] . $e['source'] . $e['target'] . $e['sourceSchema'])
->values()->toArray();
$uniqueEdgesCollection = EdgeCollection::fromArray($uniqueEdges);
$errors = $this->recordValidator->check($record->_sys->schema, $formattedData, $uniqueEdgesCollection);
} else {
$errors = $this->recordValidator->check($record->_sys->schema, $formattedData, null);
}
$newRecord = new Record(
id: $record->id,
_sys: $record->_sys->update($this->authService->currentUserId(), $status),
data: $record->data->merge($formattedData),
_file: $record->_file,
);
if ($errors->isNotEmpty()) {
$this->recordValidator->throwException($errors);
}
RecordRepo::update($newRecord);
if ($updateEdges) {
EdgeRepo::update($newRecord->id, $uniqueEdgesCollection);
}
$this->revisionService->create($newRecord);
}
/**
*/
public
function changeStatusBulk(
string $status,
array $recordsIds,
): void
{
$recordsStatus = (new RecordStatus($status));
RecordRepo::updateStatusBulk($recordsStatus, $recordsIds);
}
/**
* @throws LucentException
* @throws ValidatorException
*/
public
function clone(
string $recordId,
): string
{
$queryResult = $this->query
->filter(["id" => $recordId])
->limit(1)
->childrenDepth(1)
->runWithCount();
$graph = $queryResult->getQueryRecords();
$record = $graph->records[0] ?? null;
if (empty($record)) {
throw new LucentException("Record id is missing");
}
$newRecordId = (string)Str::uuid();
$newEdgesData = $graph->edges
->filter(fn(Edge $edge) => $edge->source == $recordId)
->values()
->map(function (Edge $edge) use ($newRecordId) {
$edge->source = $newRecordId;
return $edge->toArray();
})->toArray();
$record->id = $newRecordId;
return $this->create(
schemaName: $record->_sys->schema,
data: $record->data->toArray(),
id: $record->id,
file: $record->_file?->toArray() ?? [],
edges: $newEdgesData,
status: "draft"
);
}
public function deleteMany(
array $recordsIds,
): void
{
RecordRepo::deleteMany($recordsIds);
}
/**
* @throws LucentException
* @throws ValidatorException
*/
public function rollback(
string $userId,
string $recordId,
int $version,
): void
{
$revision = $this->revisionService->getByRecordIdAndVersion($recordId, $version)->get();
$this->update(
userId: $userId,
id: $revision->recordId,
data: $revision->data->toArray(),
status: $revision->_sys->status,
updateEdges: false
);
}
public function createEmpty(
Schema $schema,
string $userId,
): Record
{
$defaultValues = $schema->fields->reduce(function ($carry, FieldInterface $f) {
$carry[$f->name] = $f->default ?? null;
return $carry;
}, []);
$formattedData = $this->inputFormatter->fill($schema->name, new RecordData($defaultValues));
return new Record(
id: Id::new(),
_sys: System::newRecord($schema, $userId),
data: $formattedData,
_file: null,
);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Lucent\Record;
class RecordStatus
{
private string $value;
public function __construct(
public readonly ?string $status = null,
)
{
if (empty($status)) {
$this->value = "draft";
return;
}
$validStatuses = ["trashed", "published", "draft"];
if (!in_array($status, $validStatuses)) {
$status = "draft";
}
$this->value = $status;
}
public function value(): string
{
return $this->value;
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace Lucent\Record;
use Carbon\Carbon;
use Lucent\Schema\Schema;
class System
{
function __construct(
public readonly string $schema,
public readonly int $version,
public readonly string $status,
public readonly string $createdBy,
public readonly string $updatedBy,
public readonly string $createdAt,
public readonly string $updatedAt,
)
{
}
public static function fromArray(array $data): System
{
return new System(
schema: data_get($data, "schema"),
version: data_get($data, "version"),
status: data_get($data, "status"),
createdBy: data_get($data, "createdBy"),
updatedBy: data_get($data, "updatedBy"),
createdAt: data_get($data, "createdAt"),
updatedAt: data_get($data, "updatedAt"),
);
}
public static function newRecord(Schema $schema, string $userId, ?string $status = null): System
{
$now = Carbon::now()->toJson();
return new System(
schema: $schema->name,
version: 1,
status: (new RecordStatus($status))->value(),
createdBy: $userId,
updatedBy: $userId,
createdAt: $now,
updatedAt: $now,
);
}
public function update(string $userId, ?string $status = null): System
{
$now = Carbon::now()->toJson();
$newStatus = $status ?? $this->status;
return new System(
schema: $this->schema,
version: $this->version + 1,
status: (new RecordStatus($newStatus))->value(),
createdBy: $this->createdBy,
updatedBy: $userId,
createdAt: $this->createdAt,
updatedAt: $now,
);
}
}