diff --git a/front/js/entry/FieldEditEntry/FieldEditEntry.svelte b/front/js/entry/FieldEditEntry/FieldEditEntry.svelte index c53c274..c34439d 100644 --- a/front/js/entry/FieldEditEntry/FieldEditEntry.svelte +++ b/front/js/entry/FieldEditEntry/FieldEditEntry.svelte @@ -1,6 +1,7 @@ + +
+ + + + +
diff --git a/front/js/entry/RecordEditEntry/RecordForm.svelte b/front/js/entry/RecordEditEntry/RecordForm.svelte index 8a186aa..e125c76 100644 --- a/front/js/entry/RecordEditEntry/RecordForm.svelte +++ b/front/js/entry/RecordEditEntry/RecordForm.svelte @@ -1,5 +1,6 @@ {#each fields as field}
{#if field.type === "text"} - f.fieldId === field.id && f.locale === "main", - )} - schemaField={field} - locale="main" - dataField={fieldData.find( - (f) => f.id === field.id && f.locale === "main", - )} - > + {@render textField(field, "main")} {#if field.translatable} {#each selectedLocales as locale (locale)} - - f.fieldId === field.id && f.locale === locale, - )} - schemaField={field} - {locale} - dataField={fieldData.find( - (f) => f.id === field.id && f.locale === locale, - )} - > + {@render textField(field, locale)} + {/each} + {/if} + {/if} + {#if field.type === "relation"} + {@render relationField(field, "main")} + {#if field.translatable} + {#each selectedLocales as locale (locale)} + {@render relationField(field, locale)} {/each} {/if} {/if}
{/each} + +{#snippet textField(field, locale)} + +{/snippet} + +{#snippet relationField(field, locale)} + +{/snippet} diff --git a/front/js/entry/RecordEditEntry/fields/RelationField.svelte b/front/js/entry/RecordEditEntry/fields/RelationField.svelte new file mode 100644 index 0000000..c088bbb --- /dev/null +++ b/front/js/entry/RecordEditEntry/fields/RelationField.svelte @@ -0,0 +1,86 @@ + + +
+ +
diff --git a/front/js/entry/SchemaEntry/SchemaEntry.svelte b/front/js/entry/SchemaEntry/SchemaEntry.svelte index afb67e5..0ba614e 100644 --- a/front/js/entry/SchemaEntry/SchemaEntry.svelte +++ b/front/js/entry/SchemaEntry/SchemaEntry.svelte @@ -8,7 +8,8 @@ let newSchemaAlias = $state(""); let fields = $state(data.fields); const app = getApp(); - + const createFieldUrl = (schema, type) => + app.url(`fields/create?schema=${schema.id}&type=${type}`); function handleSchemaCreate(e) { e.preventDefault(); post( @@ -113,24 +114,16 @@ Add field diff --git a/src/Core/Data/Record.php b/src/Core/Data/Record.php index f1b9911..b6addb1 100644 --- a/src/Core/Data/Record.php +++ b/src/Core/Data/Record.php @@ -7,6 +7,7 @@ class Record public function __construct( public string $id, public string $schemaId, + public string $titleFieldId, public Carbon $createdAt, public string $createdBy, public ?Carbon $publishedAt, diff --git a/src/Core/Data/RecordField.php b/src/Core/Data/RecordField.php index 3a9771b..3318bf5 100644 --- a/src/Core/Data/RecordField.php +++ b/src/Core/Data/RecordField.php @@ -5,8 +5,9 @@ use Carbon\Carbon; class RecordField { public function __construct( - public string $recordId, public string $id, + public string $recordId, + public string $fieldId, public string $locale, public RecordMode $mode, public mixed $value, diff --git a/src/Core/Data/RecordPreview.php b/src/Core/Data/RecordPreview.php new file mode 100644 index 0000000..0e3a522 --- /dev/null +++ b/src/Core/Data/RecordPreview.php @@ -0,0 +1,10 @@ + $field->recordId, "id" => $field->id, + "record_id" => $field->recordId, + "field_id" => $field->fieldId, "locale" => $field->locale, "mode" => $field->mode->value, "value" => $field->value, diff --git a/src/Core/Record/RecordModule.php b/src/Core/Record/RecordModule.php index 7647f48..8449632 100644 --- a/src/Core/Record/RecordModule.php +++ b/src/Core/Record/RecordModule.php @@ -2,7 +2,7 @@ use Carbon\Carbon; use Lucent\Core\Data\Record; -use Lucent\Core\Data\RecordField; +use Lucent\Core\Data\RecordPreview; use Lucent\Core\Data\RecordStatus; use stdClass; @@ -27,6 +27,7 @@ class RecordModule return [ "id" => $record->id, "schema_id" => $record->schemaId, + "title_field_id" => $record->titleFieldId, "created_at" => $record->createdAt->toJSON(), "created_by" => $record->createdBy, "published_at" => empty($record->publishedAt) @@ -49,6 +50,7 @@ class RecordModule return new Record( id: data_get($data, "id"), schemaId: data_get($data, "schema_id"), + titleFieldId: data_get($data, "title_field_id"), createdAt: Carbon::parse(data_get($data, "created_at")), createdBy: data_get($data, "created_by"), publishedAt: empty(data_get($data, "published_at")) @@ -65,4 +67,13 @@ class RecordModule trashedBy: data_get($data, "trashed_by"), ); } + + public static function recordPreviewFromDb(stdClass $data): RecordPreview + { + return new RecordPreview( + id: data_get($data, "id"), + schemaId: data_get($data, "schema_id"), + title: data_get($data, "title"), + ); + } } diff --git a/src/Core/Record/RecordValidationModule.php b/src/Core/Record/RecordValidationModule.php index 1fe74f2..5bde0d6 100644 --- a/src/Core/Record/RecordValidationModule.php +++ b/src/Core/Record/RecordValidationModule.php @@ -157,7 +157,7 @@ class RecordValidationModule string $locale, ): ?RecordField { return collect($recordFields)->first( - fn(RecordField $field) => $field->id === $schemaField->id && + fn(RecordField $field) => $field->fieldId === $schemaField->id && $field->locale === $locale && $field->mode === RecordMode::DRAFT, ); diff --git a/src/Core/Repository/RecordFieldRepo.php b/src/Core/Repository/RecordFieldRepo.php index f237668..aa5c6a6 100644 --- a/src/Core/Repository/RecordFieldRepo.php +++ b/src/Core/Repository/RecordFieldRepo.php @@ -34,7 +34,7 @@ class RecordFieldRepo public static function delete(RecordField $field): void { DB::table(self::TABLE_NAME) - ->where("id", $field->id) + ->where("field_id", $field->fieldId) ->where("locale", $field->locale) ->where("mode", $field->mode->value) ->where("record_id", $field->recordId) @@ -44,7 +44,7 @@ class RecordFieldRepo public static function findOne(string $id): ?RecordField { return DB::table(self::TABLE_NAME) - ->where("id", $id) + ->where("field_id", $id) ->get() ->map(RecordFieldModule::fromDb(...)) ->first(); diff --git a/src/Core/Repository/RecordRepo.php b/src/Core/Repository/RecordRepo.php index 1778ce0..c9e9b77 100644 --- a/src/Core/Repository/RecordRepo.php +++ b/src/Core/Repository/RecordRepo.php @@ -2,6 +2,7 @@ use Illuminate\Support\Facades\DB; use Lucent\Core\Data\Record; +use Lucent\Core\Data\RecordPreview; use Lucent\Core\Record\RecordModule; class RecordRepo @@ -28,4 +29,33 @@ class RecordRepo ->map(RecordModule::fromDb(...)) ->first(); } + + /* + * @return RecordPreview[] + */ + public static function findBySchemas(array $schemas): array + { + return DB::table(self::TABLE_NAME) + ->select( + "records.id", + "records.schema_id", + "records_data.value as title", + ) + ->whereIn("schema_id", $schemas) + ->join("records_data", function ($join) { + $join + ->on("records.id", "=", "records_data.record_id") + ->on( + "records.title_field_id", + "=", + "records_data.field_id", + ); + }) + ->orderBy("created_at", "desc") + ->limit(5) + ->get() + + ->map(RecordModule::recordPreviewFromDb(...)) + ->toArray(); + } } diff --git a/src/Core/Schema/Data/FieldProp/FieldProp.php b/src/Core/Schema/Data/FieldProp/FieldProp.php index 9a352b0..27022c8 100644 --- a/src/Core/Schema/Data/FieldProp/FieldProp.php +++ b/src/Core/Schema/Data/FieldProp/FieldProp.php @@ -6,6 +6,7 @@ class FieldProp { return match ($type) { "text" => new TextFieldProp(min: 0, max: 0, default: ""), + "relation" => new RelationFieldProp(schemas: [], min: 0, max: 0), default => new InvalidFieldProp(), }; } @@ -24,6 +25,11 @@ class FieldProp max: $props["max"] ?? 0, default: $props["default"] ?? "", ), + "relation" => new RelationFieldProp( + schemas: $props["schemas"] ?? [], + min: $props["min"] ?? 0, + max: $props["max"] ?? 0, + ), default => new InvalidFieldProp(), }; } diff --git a/src/Core/Schema/Data/FieldProp/RelationFieldProp.php b/src/Core/Schema/Data/FieldProp/RelationFieldProp.php new file mode 100644 index 0000000..8ab7aa3 --- /dev/null +++ b/src/Core/Schema/Data/FieldProp/RelationFieldProp.php @@ -0,0 +1,10 @@ +edgeService->createMany( - source: $request->input("source"), - sourceSchema: $request->input("sourceSchema"), - targetSchema: $request->input("targetSchema"), - field: $request->input("field"), - targets: $request->input("targets"), - ); - $graph = $this->query - ->filter(["id" => $request->input("source")]) - ->limit(1) - ->skip(0) - ->childrenDepth(2) - ->childrenLimit(200) - ->parentsDepth(1) - ->parentsLimit(200) - ->run(); - - return [ - "graph" => toArray($graph), - ]; - } - + public function postCreate(Request $request) {} } diff --git a/src/Http/Controller/RecordController.php b/src/Http/Controller/RecordController.php index c14687a..18c50fb 100644 --- a/src/Http/Controller/RecordController.php +++ b/src/Http/Controller/RecordController.php @@ -136,96 +136,6 @@ class RecordController ); } - public function exportCSV(Request $request) - { - $schemaName = $request->route("schemaName"); - $schema = $this->channelService->channel->schemas - ->where("name", $schemaName) - ->first(); - - $urlParams = $request->all(); - - $sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt"; - $filter = data_get($urlParams, "filter") ?? []; - $arguments = array_merge( - [ - "schema" => $schema->name, - "status_in" => "draft,published", - ], - $filter, - ); - - $records = $this->query - ->filter($arguments) - ->limit(-1) - ->status(explode(",", $arguments["status_in"])) - ->childrenDepth(1) - // ->skip($skip) - ->sort($sort) - ->run() - ->tree(); - - header("Content-Type: application/csv"); - header( - 'Content-Disposition: attachment; filename="' . - $schemaName . - '.csv";', - ); - $handle = fopen("php://output", "w"); - $relationColumns = $this->makeCsvRelationColumns($schema); - $csvRow = [ - "id", - ...array_keys($records[0]->data->toArray()), - ...$relationColumns, - ]; - fputcsv($handle, $csvRow, ","); - foreach ($records as $record) { - $csvRow = [$record->id, ...$record->data->toArray()]; - $csvRow = array_merge( - $csvRow, - $this->makeCsvRelationColumnValues($schema, $record->_children), - ); - $csvRow = array_values($csvRow); - fputcsv($handle, $csvRow, ","); - } - fclose($handle); - echo $handle; - exit(); - } - - private function makeCsvRelationColumns($schema): array - { - return $schema->fields - ->filter(fn($f) => get_class($f) === Reference::class) - ->reduce(function ($c, $f) { - $c[] = $f->name . " id"; - $c[] = $f->name . " name"; - return $c; - }, []); - } - - private function makeCsvRelationColumnValues($schema, $children): array - { - return $schema->fields - ->filter(fn($f) => get_class($f) === Reference::class) - ->reduce(function ($c, $f) use ($children) { - $fieldRecords = data_get($children, $f->name); - if (empty($fieldRecords)) { - $c[] = ""; - $c[] = ""; - } elseif (count($fieldRecords) === 1) { - $c[] = data_get($fieldRecords, "0.id"); - $c[] = $this->viewModel->getRecordName($fieldRecords[0]); - } else { - $c[] = collect($fieldRecords)->pluck("id")->join("::"); - $c[] = collect($fieldRecords) - ->pluck("data.name") - ->join("::"); - } - return $c; - }, []); - } - public function edit(Request $request) { $recordId = $request->route("id"); @@ -291,34 +201,11 @@ class RecordController ); } - public function suggestions(Request $request) + public function suggest(Request $request) { - $arguments = [ - "schema" => $request->input("schema"), - ]; - - if ($request->input("value")) { - if (in_array($request->input("ui"), ["text", "date"])) { - $arguments[ - "data." . $request->input("field") . "_regex" - ] = $request->input("value"); - } elseif ($request->input("ui") == "number") { - $arguments[ - "data." . $request->input("field") . "_eqnum" - ] = floatval($request->input("value")); - } elseif ($request->input("ui") == "date") { - } elseif ($request->input("ui") == "search") { - $arguments["search_regex"] = $request->input("value"); - } - } - - $records = $this->query->filter($arguments)->limit(10)->tree(); - - if ($records->isEmpty()) { - return ok([]); - } - - return ok($records->toArray()); + $schemas = $request->input("schemas"); + $records = RecordRepo::findBySchemas($schemas); + return ok(toArray($records)); } public function postCreate(Request $request) @@ -328,10 +215,11 @@ class RecordController $title = $request->input("title"); $now = Carbon::now(); $userId = AuthModule::getCurrentUserId(); - + $titleField = collect($fields)->where("alias", "_title")->first(); $record = new Record( id: Id::new(), schemaId: $schemaId, + titleFieldId: $titleField->id, createdAt: $now, createdBy: $userId, publishedAt: null, @@ -344,11 +232,10 @@ class RecordController RecordRepo::insert($record); - $titleField = collect($fields)->where("alias", "_title")->first(); - $titleFieldData = new RecordField( + id: Id::new(), recordId: $record->id, - id: $titleField->id, + fieldId: $titleField->id, locale: "main", mode: RecordMode::DRAFT, value: $title, @@ -364,15 +251,16 @@ class RecordController public function saveField(Request $request) { $recordId = $request->input("recordId"); - $id = $request->input("id"); + $fieldId = $request->input("id"); $locale = $request->input("locale"); $value = $request->input("value") ?? ""; $now = Carbon::now(); - $field = FieldRepo::findOne($id); + $field = FieldRepo::findOne($fieldId); $userId = AuthModule::getCurrentUserId(); $recordField = new RecordField( + id: Id::new(), recordId: $recordId, - id: $field->id, + fieldId: $field->id, locale: $locale, mode: RecordMode::DRAFT, value: $value, @@ -396,49 +284,6 @@ class RecordController ]); } - public function save(Request $request) - { - $recordId = $request->input("record.id"); - try { - if ($request->input("isCreateMode")) { - $recordId = $this->recordService->create( - data: new RecordInputData( - $request->input("record.schema"), - $recordId ?? "", - $request->input("record.data"), - Status::from($request->input("record.status")), - ), - edges: array_map( - EdgeInputData::fromArray(...), - $request->input("edges") ?? [], - ), - ); - } else { - $this->recordService->updateWithEdges( - id: $request->input("record.id"), - data: $request->input("record.data"), - status: Status::from($request->input("record.status")), - edges: array_map( - EdgeInputData::fromArray(...), - $request->input("edges") ?? [], - ), - ); - } - - $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); - } - return ok(toArray($newGraph)); - } - public function clone(Request $request) { try { @@ -596,4 +441,94 @@ class RecordController return ok(toArray($record)); } + + public function exportCSV(Request $request) + { + $schemaName = $request->route("schemaName"); + $schema = $this->channelService->channel->schemas + ->where("name", $schemaName) + ->first(); + + $urlParams = $request->all(); + + $sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt"; + $filter = data_get($urlParams, "filter") ?? []; + $arguments = array_merge( + [ + "schema" => $schema->name, + "status_in" => "draft,published", + ], + $filter, + ); + + $records = $this->query + ->filter($arguments) + ->limit(-1) + ->status(explode(",", $arguments["status_in"])) + ->childrenDepth(1) + // ->skip($skip) + ->sort($sort) + ->run() + ->tree(); + + header("Content-Type: application/csv"); + header( + 'Content-Disposition: attachment; filename="' . + $schemaName . + '.csv";', + ); + $handle = fopen("php://output", "w"); + $relationColumns = $this->makeCsvRelationColumns($schema); + $csvRow = [ + "id", + ...array_keys($records[0]->data->toArray()), + ...$relationColumns, + ]; + fputcsv($handle, $csvRow, ","); + foreach ($records as $record) { + $csvRow = [$record->id, ...$record->data->toArray()]; + $csvRow = array_merge( + $csvRow, + $this->makeCsvRelationColumnValues($schema, $record->_children), + ); + $csvRow = array_values($csvRow); + fputcsv($handle, $csvRow, ","); + } + fclose($handle); + echo $handle; + exit(); + } + + private function makeCsvRelationColumns($schema): array + { + return $schema->fields + ->filter(fn($f) => get_class($f) === Reference::class) + ->reduce(function ($c, $f) { + $c[] = $f->name . " id"; + $c[] = $f->name . " name"; + return $c; + }, []); + } + + private function makeCsvRelationColumnValues($schema, $children): array + { + return $schema->fields + ->filter(fn($f) => get_class($f) === Reference::class) + ->reduce(function ($c, $f) use ($children) { + $fieldRecords = data_get($children, $f->name); + if (empty($fieldRecords)) { + $c[] = ""; + $c[] = ""; + } elseif (count($fieldRecords) === 1) { + $c[] = data_get($fieldRecords, "0.id"); + $c[] = $this->viewModel->getRecordName($fieldRecords[0]); + } else { + $c[] = collect($fieldRecords)->pluck("id")->join("::"); + $c[] = collect($fieldRecords) + ->pluck("data.name") + ->join("::"); + } + return $c; + }, []); + } } diff --git a/src/Http/web.php b/src/Http/web.php index 8453a01..afe4359 100644 --- a/src/Http/web.php +++ b/src/Http/web.php @@ -70,6 +70,7 @@ Route::group( Route::get("content/{id}", [RecordController::class, "index"]); // RECORD + Route::get("records/suggest", [RecordController::class, "suggest"]); Route::get("records/{id}", [RecordController::class, "edit"]); Route::post("records", [RecordController::class, "postCreate"]); Route::post("records/fields", [ diff --git a/src/Record/Record.php b/src/Record/Record.php index bcc1947..46cdd25 100644 --- a/src/Record/Record.php +++ b/src/Record/Record.php @@ -8,30 +8,37 @@ use Illuminate\Support\Str; class Record implements JsonSerializable { - - function __construct( - public string $id, - public string $schema, - public Status $status, - public System $_sys, + public string $id, + public string $schema, + public Status $status, + public System $_sys, public RecordData $data, - public ?FileData $_file = null, - ) + public ?FileData $_file = null, + ) {} + + private function indexValues(array $arrObject) { - } - - - private function indexValues(array $arrObject){ - - return trim(Str::lower(collect($arrObject) - ->map(function($value){ - if(is_array($value)){ - return $this->indexValues($value ?? []); - } - return str_replace(array("\r", "\n"), '', strip_tags((string)$value)); - }) - ->values()->join(" ")." ". $this->_file?->originalName ?? "")); + return trim( + Str::lower( + collect($arrObject) + ->map(function ($value) { + if (is_array($value)) { + return $this->indexValues($value ?? []); + } + return str_replace( + ["\r", "\n"], + "", + strip_tags((string) $value), + ); + }) + ->values() + ->join(" ") . + " " . + $this->_file?->originalName ?? + "", + ), + ); } public function toDB(): array @@ -50,10 +57,8 @@ class Record implements JsonSerializable public static function fromDB(stdClass $data): Record { - $file = json_decode($data->_file, true); if (!empty($file)) { - $file = FileData::fromArray($file); } else { $file = null; @@ -69,11 +74,8 @@ class Record implements JsonSerializable ); } - public function jsonSerialize(): static { return $this; } - - }