2023-10-02 23:10:49 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Lucent\Record;
|
|
|
|
|
|
2024-08-19 17:48:10 +03:00
|
|
|
use Illuminate\Http\UploadedFile;
|
2023-10-02 23:10:49 +03:00
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
use Lucent\Account\AuthService;
|
|
|
|
|
use Lucent\Channel\ChannelService;
|
|
|
|
|
use Lucent\Edge\Edge;
|
2023-11-29 17:10:15 +02:00
|
|
|
use Lucent\Edge\EdgeService;
|
2023-10-02 23:10:49 +03:00
|
|
|
use Lucent\File\FileService;
|
|
|
|
|
use Lucent\Id\Id;
|
|
|
|
|
use Lucent\LucentException;
|
|
|
|
|
use Lucent\Query\Query;
|
2024-08-19 17:48:10 +03:00
|
|
|
use Lucent\Record\InputData\EdgeInputData;
|
|
|
|
|
use Lucent\Record\InputData\RecordInputData;
|
2023-10-02 23:10:49 +03:00
|
|
|
use Lucent\Revision\RevisionService;
|
|
|
|
|
use Lucent\Schema\FieldInterface;
|
|
|
|
|
use Lucent\Schema\Schema;
|
2024-08-19 17:48:10 +03:00
|
|
|
use Lucent\Schema\Type;
|
2023-10-02 23:10:49 +03:00
|
|
|
use Lucent\Schema\Validator\Validator;
|
|
|
|
|
use Lucent\Schema\Validator\ValidatorException;
|
|
|
|
|
|
|
|
|
|
readonly class RecordService
|
|
|
|
|
{
|
|
|
|
|
public function __construct(
|
2026-04-20 21:07:35 +03:00
|
|
|
private AuthService $authService,
|
2023-10-02 23:10:49 +03:00
|
|
|
private RevisionService $revisionService,
|
2026-04-20 21:07:35 +03:00
|
|
|
private ChannelService $channelService,
|
|
|
|
|
private Validator $recordValidator,
|
|
|
|
|
private Query $query,
|
|
|
|
|
private InputFormatter $inputFormatter,
|
|
|
|
|
private RecordRepo $recordRepo,
|
|
|
|
|
private EdgeService $edgeService,
|
|
|
|
|
private FileService $fileService,
|
|
|
|
|
) {}
|
2023-10-02 23:10:49 +03:00
|
|
|
|
|
|
|
|
/**
|
2024-08-19 17:48:10 +03:00
|
|
|
* @param string $url
|
|
|
|
|
* @param RecordInputData $data
|
|
|
|
|
* @param list<EdgeInputData> $edges
|
|
|
|
|
* @return string
|
2023-10-02 23:10:49 +03:00
|
|
|
* @throws LucentException
|
|
|
|
|
* @throws ValidatorException
|
|
|
|
|
*/
|
2024-08-19 17:48:10 +03:00
|
|
|
public function createFromUrl(
|
2026-04-20 21:07:35 +03:00
|
|
|
string $url,
|
2024-08-19 17:48:10 +03:00
|
|
|
RecordInputData $data,
|
2026-04-20 21:07:35 +03:00
|
|
|
array $edges,
|
|
|
|
|
): string {
|
2024-08-19 17:48:10 +03:00
|
|
|
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
|
|
|
|
if ($schema->type !== Type::FILES) {
|
2026-04-20 21:07:35 +03:00
|
|
|
throw new LucentException(
|
|
|
|
|
"You can't upload a file to a regular record",
|
|
|
|
|
);
|
2024-08-19 17:48:10 +03:00
|
|
|
}
|
|
|
|
|
$fileData = $this->fileService->createFromUrl($schema, $url);
|
2024-08-19 17:59:08 +03:00
|
|
|
if ($fileData->isDuplicate) {
|
|
|
|
|
return $fileData->duplicateId;
|
|
|
|
|
}
|
2024-08-19 17:48:10 +03:00
|
|
|
return $this->create($data, $fileData->recordFile, $edges);
|
|
|
|
|
}
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2024-08-19 17:48:10 +03:00
|
|
|
public function createFromUploadedFile(
|
2026-04-20 21:07:35 +03:00
|
|
|
UploadedFile $uploadedFile,
|
2024-08-19 17:48:10 +03:00
|
|
|
RecordInputData $data,
|
2026-04-20 21:07:35 +03:00
|
|
|
array $edges,
|
|
|
|
|
): string {
|
2024-08-19 17:48:10 +03:00
|
|
|
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
|
|
|
|
if ($schema->type !== Type::FILES) {
|
2026-04-20 21:07:35 +03:00
|
|
|
throw new LucentException(
|
|
|
|
|
"You can't upload a file to a regular record",
|
|
|
|
|
);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
2024-08-19 17:48:10 +03:00
|
|
|
$fileData = $this->fileService->upload($schema, $uploadedFile);
|
2024-08-19 17:59:08 +03:00
|
|
|
if ($fileData->isDuplicate) {
|
|
|
|
|
return $fileData->duplicateId;
|
|
|
|
|
}
|
2024-08-19 17:48:10 +03:00
|
|
|
return $this->create($data, $fileData->recordFile, $edges);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function createFromFileData(
|
2026-04-20 21:07:35 +03:00
|
|
|
FileData $fileData,
|
2024-08-19 17:48:10 +03:00
|
|
|
RecordInputData $data,
|
2026-04-20 21:07:35 +03:00
|
|
|
array $edges,
|
|
|
|
|
): string {
|
2024-08-19 17:48:10 +03:00
|
|
|
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
|
|
|
|
if ($schema->type !== Type::FILES) {
|
2026-04-20 21:07:35 +03:00
|
|
|
throw new LucentException(
|
|
|
|
|
"You can't upload a file to a regular record",
|
|
|
|
|
);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
2024-08-19 17:48:10 +03:00
|
|
|
return $this->create($data, $fileData, $edges);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param RecordInputData $data
|
|
|
|
|
* @param FileData|null $file
|
|
|
|
|
* @param list<EdgeInputData> $edges
|
|
|
|
|
* @return string
|
|
|
|
|
* @throws ValidatorException
|
|
|
|
|
*/
|
|
|
|
|
public function create(
|
|
|
|
|
RecordInputData $data,
|
2026-04-20 21:07:35 +03:00
|
|
|
?FileData $file = null,
|
|
|
|
|
array $edges = [],
|
|
|
|
|
): string {
|
|
|
|
|
$formattedData = $this->inputFormatter->fill(
|
|
|
|
|
$data->schemaName,
|
|
|
|
|
new RecordData($data->data),
|
|
|
|
|
);
|
2024-08-19 17:48:10 +03:00
|
|
|
$newRecordId = empty($data->id) ? Id::new() : $data->id;
|
2023-10-02 23:10:49 +03:00
|
|
|
|
|
|
|
|
$record = new Record(
|
2024-01-11 22:57:57 +02:00
|
|
|
id: $newRecordId,
|
2024-08-19 17:48:10 +03:00
|
|
|
schema: $data->schemaName,
|
|
|
|
|
status: $data->status,
|
2023-10-04 13:32:30 +03:00
|
|
|
_sys: System::newRecord($this->authService->currentUserId()),
|
2023-10-02 23:10:49 +03:00
|
|
|
data: $formattedData,
|
2024-08-19 17:48:10 +03:00
|
|
|
_file: !empty($file) ? FileData::fromArray(toArray($file)) : null,
|
2023-10-02 23:10:49 +03:00
|
|
|
);
|
|
|
|
|
|
2024-08-19 17:48:10 +03:00
|
|
|
if ($data->status === Status::PUBLISHED) {
|
2026-04-20 21:07:35 +03:00
|
|
|
$errors = $this->recordValidator->check(
|
|
|
|
|
$data->schemaName,
|
|
|
|
|
$record->data,
|
|
|
|
|
);
|
2023-10-13 21:06:23 +03:00
|
|
|
if ($errors->isNotEmpty()) {
|
|
|
|
|
$this->recordValidator->throwException($errors);
|
|
|
|
|
}
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RecordRepo::create($record);
|
2026-04-20 21:07:35 +03:00
|
|
|
$newEdges = $this->edgeService->createManyForRecord(
|
|
|
|
|
$record->id,
|
|
|
|
|
$record->schema,
|
|
|
|
|
$edges,
|
|
|
|
|
);
|
2024-08-19 17:48:10 +03:00
|
|
|
$this->revisionService->create($record, $newEdges);
|
2023-10-02 23:10:49 +03:00
|
|
|
return $record->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws LucentException
|
|
|
|
|
* @throws ValidatorException
|
|
|
|
|
*/
|
2026-04-20 21:07:35 +03:00
|
|
|
public function update(string $id, array $data, Status $status): void
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2026-04-20 21:07:35 +03:00
|
|
|
$record = $this->query
|
|
|
|
|
->filter(["id" => $id])
|
|
|
|
|
->run()
|
|
|
|
|
->records->first();
|
2023-10-02 23:10:49 +03:00
|
|
|
|
|
|
|
|
if (empty($record)) {
|
|
|
|
|
throw new LucentException("Record id is missing");
|
|
|
|
|
}
|
2026-04-20 21:07:35 +03:00
|
|
|
$formattedData = $this->inputFormatter->fill(
|
|
|
|
|
$record->schema,
|
|
|
|
|
new RecordData($data),
|
|
|
|
|
);
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2024-08-19 17:48:10 +03:00
|
|
|
if ($status === Status::PUBLISHED) {
|
2026-04-20 21:07:35 +03:00
|
|
|
$errors = $this->recordValidator->check(
|
|
|
|
|
$record->schema,
|
|
|
|
|
$formattedData,
|
|
|
|
|
);
|
2023-10-13 21:06:23 +03:00
|
|
|
if ($errors->isNotEmpty()) {
|
|
|
|
|
$this->recordValidator->throwException($errors);
|
|
|
|
|
}
|
2024-08-19 17:48:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$newRecord = new Record(
|
|
|
|
|
id: $record->id,
|
|
|
|
|
schema: $record->schema,
|
|
|
|
|
status: $status,
|
|
|
|
|
_sys: $record->_sys->update($this->authService->currentUserId()),
|
|
|
|
|
data: $record->data->merge($formattedData),
|
|
|
|
|
_file: $record->_file,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
RecordRepo::update($newRecord);
|
|
|
|
|
$newEdges = $this->edgeService->findForSource($record->id);
|
|
|
|
|
$this->revisionService->create($newRecord, $newEdges);
|
|
|
|
|
}
|
2023-10-13 21:06:23 +03:00
|
|
|
|
2024-08-19 17:48:10 +03:00
|
|
|
/**
|
|
|
|
|
* @throws LucentException
|
|
|
|
|
* @throws ValidatorException
|
|
|
|
|
*/
|
|
|
|
|
public function updateWithEdges(
|
|
|
|
|
string $id,
|
2026-04-20 21:07:35 +03:00
|
|
|
array $data,
|
2024-08-19 17:48:10 +03:00
|
|
|
Status $status,
|
2026-04-20 21:07:35 +03:00
|
|
|
array $edges,
|
|
|
|
|
): void {
|
|
|
|
|
$record = $this->query
|
|
|
|
|
->filter(["id" => $id])
|
|
|
|
|
->run()
|
|
|
|
|
->records->first();
|
2024-08-19 17:48:10 +03:00
|
|
|
|
|
|
|
|
if (empty($record)) {
|
|
|
|
|
throw new LucentException("Record id is missing");
|
|
|
|
|
}
|
2026-04-20 21:07:35 +03:00
|
|
|
$formattedData = $this->inputFormatter->fill(
|
|
|
|
|
$record->schema,
|
|
|
|
|
new RecordData($data),
|
|
|
|
|
);
|
2024-08-19 17:48:10 +03:00
|
|
|
|
|
|
|
|
if ($status === Status::PUBLISHED) {
|
2026-04-20 21:07:35 +03:00
|
|
|
$errors = $this->recordValidator->check(
|
|
|
|
|
$record->schema,
|
|
|
|
|
$formattedData,
|
|
|
|
|
);
|
2023-10-13 21:06:23 +03:00
|
|
|
if ($errors->isNotEmpty()) {
|
|
|
|
|
$this->recordValidator->throwException($errors);
|
|
|
|
|
}
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$newRecord = new Record(
|
|
|
|
|
id: $record->id,
|
2023-10-04 13:32:30 +03:00
|
|
|
schema: $record->schema,
|
2024-08-19 17:48:10 +03:00
|
|
|
status: $status,
|
2023-10-04 13:32:30 +03:00
|
|
|
_sys: $record->_sys->update($this->authService->currentUserId()),
|
2023-10-02 23:10:49 +03:00
|
|
|
data: $record->data->merge($formattedData),
|
|
|
|
|
_file: $record->_file,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
RecordRepo::update($newRecord);
|
2026-04-20 21:07:35 +03:00
|
|
|
$newEdges = $this->edgeService->replaceManyForRecord(
|
|
|
|
|
$record->id,
|
|
|
|
|
$record->schema,
|
|
|
|
|
$edges,
|
|
|
|
|
);
|
2024-08-19 17:48:10 +03:00
|
|
|
$this->revisionService->create($newRecord, $newEdges);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
2026-04-20 21:07:35 +03:00
|
|
|
public function changeStatusBulk(string $status, array $recordsIds): void
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2023-10-04 13:32:30 +03:00
|
|
|
RecordRepo::updateStatusBulk(Status::from($status), $recordsIds);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws LucentException
|
|
|
|
|
* @throws ValidatorException
|
|
|
|
|
*/
|
2026-04-20 21:07:35 +03:00
|
|
|
public function clone(string $recordId): string
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2023-10-04 13:32:30 +03:00
|
|
|
$graph = $this->query
|
2023-10-02 23:10:49 +03:00
|
|
|
->filter(["id" => $recordId])
|
|
|
|
|
->limit(1)
|
|
|
|
|
->childrenDepth(1)
|
2023-10-04 13:32:30 +03:00
|
|
|
->run();
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2023-10-04 13:32:30 +03:00
|
|
|
$record = $graph->records->first();
|
2023-10-02 23:10:49 +03:00
|
|
|
if (empty($record)) {
|
|
|
|
|
throw new LucentException("Record id is missing");
|
|
|
|
|
}
|
2023-10-04 13:32:30 +03:00
|
|
|
|
2026-04-20 21:07:35 +03:00
|
|
|
$newRecordId = (string) Str::uuid();
|
2023-10-02 23:10:49 +03:00
|
|
|
$newEdgesData = $graph->edges
|
|
|
|
|
->filter(fn(Edge $edge) => $edge->source == $recordId)
|
|
|
|
|
->values()
|
2026-04-20 21:07:35 +03:00
|
|
|
->map(
|
|
|
|
|
fn(Edge $edge) => new EdgeInputData(
|
|
|
|
|
target: $edge->target,
|
|
|
|
|
targetSchema: $edge->targetSchema,
|
|
|
|
|
field: $edge->field,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
->toArray();
|
2023-10-02 23:10:49 +03:00
|
|
|
|
|
|
|
|
$record->id = $newRecordId;
|
|
|
|
|
|
|
|
|
|
return $this->create(
|
2024-08-19 17:48:10 +03:00
|
|
|
new RecordInputData(
|
|
|
|
|
schemaName: $record->schema,
|
|
|
|
|
id: $record->id,
|
|
|
|
|
data: $record->data->toArray(),
|
2026-04-20 21:07:35 +03:00
|
|
|
status: Status::DRAFT,
|
2024-08-19 17:48:10 +03:00
|
|
|
),
|
|
|
|
|
file: $record->_file,
|
2026-04-20 21:07:35 +03:00
|
|
|
edges: $newEdgesData,
|
2023-10-02 23:10:49 +03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 21:07:35 +03:00
|
|
|
public function deleteMany(array $recordsIds): void
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2023-10-06 18:47:50 +03:00
|
|
|
$this->recordRepo->deleteMany($recordsIds);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
2026-04-20 21:07:35 +03:00
|
|
|
public function emptyTrash(string $schemaName): void
|
2023-10-15 23:40:34 +03:00
|
|
|
{
|
|
|
|
|
$schema = $this->channelService->getSchema($schemaName)->get();
|
|
|
|
|
$this->recordRepo->deleteTrashedBySchema($schemaName);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
/**
|
|
|
|
|
* @throws LucentException
|
|
|
|
|
* @throws ValidatorException
|
|
|
|
|
*/
|
2026-04-20 21:07:35 +03:00
|
|
|
public function rollback(string $recordId, int $version): void
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2026-04-20 21:07:35 +03:00
|
|
|
$revision = $this->revisionService
|
|
|
|
|
->getByRecordIdAndVersion($recordId, $version)
|
|
|
|
|
->get();
|
2024-08-19 17:48:10 +03:00
|
|
|
$this->updateWithEdges(
|
2023-10-02 23:10:49 +03:00
|
|
|
id: $revision->recordId,
|
|
|
|
|
data: $revision->data->toArray(),
|
2024-08-19 17:48:10 +03:00
|
|
|
status: Status::DRAFT,
|
2026-04-20 21:07:35 +03:00
|
|
|
edges: array_map(
|
|
|
|
|
fn(Edge $edge) => new EdgeInputData(
|
|
|
|
|
$edge->target,
|
|
|
|
|
$edge->targetSchema,
|
|
|
|
|
$edge->field,
|
|
|
|
|
),
|
|
|
|
|
$revision->_edges,
|
|
|
|
|
),
|
2023-10-02 23:10:49 +03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 21:07:35 +03:00
|
|
|
public function createEmpty(Schema $schema): Record
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2026-04-20 21:07:35 +03:00
|
|
|
$defaultValues = $schema->fields->reduce(function (
|
|
|
|
|
$carry,
|
|
|
|
|
FieldInterface $f,
|
|
|
|
|
) {
|
2023-10-02 23:10:49 +03:00
|
|
|
$carry[$f->name] = $f->default ?? null;
|
|
|
|
|
return $carry;
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-04-20 21:07:35 +03:00
|
|
|
$formattedData = $this->inputFormatter->fill(
|
|
|
|
|
$schema->name,
|
|
|
|
|
new RecordData($defaultValues),
|
|
|
|
|
);
|
2023-10-02 23:10:49 +03:00
|
|
|
|
|
|
|
|
return new Record(
|
|
|
|
|
id: Id::new(),
|
2023-10-04 13:32:30 +03:00
|
|
|
schema: $schema->name,
|
|
|
|
|
status: Status::DRAFT,
|
2023-10-13 21:06:23 +03:00
|
|
|
_sys: System::newRecord($this->authService->currentUserId()),
|
2023-10-02 23:10:49 +03:00
|
|
|
data: $formattedData,
|
|
|
|
|
_file: null,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|