edges wip
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import SchemaLayout from "../../layouts/SchemaLayout.svelte";
|
||||
import TextFieldProps from "./TextFieldProps.svelte";
|
||||
import RelationFieldProps from "./RelationFieldProps.svelte";
|
||||
import DeleteButton from "../../common/DeleteButton.svelte";
|
||||
import { post } from "../../modules/remote";
|
||||
import { getApp } from "../../app";
|
||||
@@ -124,6 +125,9 @@
|
||||
|
||||
{#if data.field.type === "text"}
|
||||
<TextFieldProps field={data.field}></TextFieldProps>
|
||||
{:else if data.field.type === "relation"}
|
||||
<RelationFieldProps field={data.field} schemas={data.schemas}
|
||||
></RelationFieldProps>
|
||||
{/if}
|
||||
|
||||
<button type="submit">Update</button>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script>
|
||||
let { field, schemas } = $props();
|
||||
</script>
|
||||
|
||||
<fieldset>
|
||||
<label>
|
||||
Schemas
|
||||
<select
|
||||
bind:value={field.props.schemas}
|
||||
aria-label="Select allowed schemas"
|
||||
multiple
|
||||
size="6"
|
||||
>
|
||||
<option disabled>Select allowed schemas </option>
|
||||
{#each schemas as schema}
|
||||
<option value={schema.id}>{schema.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Min characters
|
||||
<input type="number" bind:value={field.props.min} />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Max characters
|
||||
<input type="number" bind:value={field.props.max} />
|
||||
</label>
|
||||
</fieldset>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import TextField from "./fields/TextField.svelte";
|
||||
import RelationField from "./fields/RelationField.svelte";
|
||||
let {
|
||||
fields,
|
||||
record,
|
||||
@@ -8,40 +9,58 @@
|
||||
fieldData,
|
||||
selectedLocales,
|
||||
} = $props();
|
||||
|
||||
const findFieldValidationError = (field, locale) => {
|
||||
return validationErrors.find(
|
||||
(f) => f.fieldId === field.id && f.locale === locale,
|
||||
);
|
||||
};
|
||||
const findDataField = (field, locale) => {
|
||||
return fieldData.find(
|
||||
(f) => f.fieldId === field.id && f.locale === locale,
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#each fields as field}
|
||||
<div style="display:flex;gap:20px;">
|
||||
{#if field.type === "text"}
|
||||
<TextField
|
||||
{channel}
|
||||
{record}
|
||||
validationError={validationErrors.find(
|
||||
(f) => f.fieldId === field.id && f.locale === "main",
|
||||
)}
|
||||
schemaField={field}
|
||||
locale="main"
|
||||
dataField={fieldData.find(
|
||||
(f) => f.id === field.id && f.locale === "main",
|
||||
)}
|
||||
></TextField>
|
||||
{@render textField(field, "main")}
|
||||
{#if field.translatable}
|
||||
{#each selectedLocales as locale (locale)}
|
||||
<TextField
|
||||
{channel}
|
||||
{record}
|
||||
validationError={validationErrors.find(
|
||||
(f) =>
|
||||
f.fieldId === field.id && f.locale === locale,
|
||||
)}
|
||||
schemaField={field}
|
||||
{locale}
|
||||
dataField={fieldData.find(
|
||||
(f) => f.id === field.id && f.locale === locale,
|
||||
)}
|
||||
></TextField>
|
||||
{@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}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#snippet textField(field, locale)}
|
||||
<TextField
|
||||
{channel}
|
||||
{record}
|
||||
validationError={findFieldValidationError(field, locale)}
|
||||
schemaField={field}
|
||||
{locale}
|
||||
dataField={findDataField(field, locale)}
|
||||
></TextField>
|
||||
{/snippet}
|
||||
|
||||
{#snippet relationField(field, locale)}
|
||||
<RelationField
|
||||
{channel}
|
||||
{record}
|
||||
validationError={findFieldValidationError(field, locale)}
|
||||
schemaField={field}
|
||||
{locale}
|
||||
dataField={findDataField(field, locale)}
|
||||
></RelationField>
|
||||
{/snippet}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<script>
|
||||
import { get, post } from "../../../modules/remote";
|
||||
import { getApp } from "../../../app";
|
||||
import { getLocaleName } from "../locale.svelte.js";
|
||||
let { channel, record, schemaField, dataField, locale, validationError } =
|
||||
$props();
|
||||
let originalValue = dataField?.value ?? schemaField.props.default;
|
||||
let newValue = $state(originalValue);
|
||||
let valuesChanged = $derived(newValue !== originalValue);
|
||||
let errorMessage = $state("");
|
||||
// let validationErrorState = $state(validationError);
|
||||
let hasError = $derived(!!validationError);
|
||||
const app = getApp();
|
||||
|
||||
let suggestionsLoaded = $state(false);
|
||||
let suggestions = $state([]);
|
||||
|
||||
function handleAddRecord(e) {
|
||||
// Add logic to handle adding a record
|
||||
if (suggestionsLoaded) {
|
||||
return;
|
||||
}
|
||||
get(
|
||||
app.url("records/suggest"),
|
||||
{ schemas: schemaField.props.schemas },
|
||||
(data, err) => {
|
||||
suggestionsLoaded = true;
|
||||
suggestions = data;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!valuesChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
post(
|
||||
app.url("records/fields"),
|
||||
{
|
||||
recordId: record.id,
|
||||
id: schemaField.id,
|
||||
locale: locale,
|
||||
value: newValue,
|
||||
},
|
||||
(data, err) => {
|
||||
if (err.isNotEmpty()) {
|
||||
errorMessage = err.first();
|
||||
} else {
|
||||
validationError = data.validationError;
|
||||
originalValue = newValue;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div style="min-width: 400px;">
|
||||
<label>
|
||||
{#if locale !== "main"}
|
||||
{getLocaleName(channel, locale)} >
|
||||
{/if}
|
||||
{schemaField.name} <br />
|
||||
<details class="dropdown">
|
||||
<summary onclick={handleAddRecord}>Add Record</summary>
|
||||
{#if suggestionsLoaded}
|
||||
<ul>
|
||||
{#each suggestions as suggestion}
|
||||
<li><a href="#">{suggestion.title}</a></li>
|
||||
{/each}
|
||||
<li><a href="#">More</a></li>
|
||||
</ul>
|
||||
{:else}
|
||||
<progress />
|
||||
{/if}
|
||||
</details>
|
||||
|
||||
{#if hasError}
|
||||
<small id={schemaField.id + "-help"}
|
||||
>{validationError.message}</small
|
||||
>
|
||||
{:else if schemaField.help != ""}
|
||||
<small id={schemaField.id + "-help"}>{schemaField.help}</small>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
@@ -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 @@
|
||||
<summary>Add field</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href={app.url(
|
||||
`fields/create?schema=${schema.id}&type=text`,
|
||||
)}>Text</a
|
||||
>
|
||||
<a href={createFieldUrl(schema, "text")}>Text</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href={`/fields/create?schema=${schema.id}&type=text`}
|
||||
>File</a
|
||||
>
|
||||
<a href={createFieldUrl(schema, "file")}>File</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={`/fields/create?schema=${schema.id}&type=text`}
|
||||
>Rich</a
|
||||
>
|
||||
<a href={createFieldUrl(schema, "relation")}>
|
||||
Relation
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php namespace Lucent\Core\Data;
|
||||
|
||||
class RecordPreview
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $schemaId,
|
||||
public string $title,
|
||||
) {}
|
||||
}
|
||||
@@ -35,8 +35,9 @@ class RecordFieldModule
|
||||
public static function fromDb(stdClass $field): RecordField
|
||||
{
|
||||
return new RecordField(
|
||||
recordId: data_get($field, "record_id"),
|
||||
id: data_get($field, "id"),
|
||||
recordId: data_get($field, "record_id"),
|
||||
fieldId: data_get($field, "field_id"),
|
||||
locale: data_get($field, "locale", []),
|
||||
mode: RecordMode::from(data_get($field, "mode")),
|
||||
value: data_get($field, "value"),
|
||||
@@ -48,8 +49,9 @@ class RecordFieldModule
|
||||
public static function toDb(RecordField $field): array
|
||||
{
|
||||
return [
|
||||
"record_id" => $field->recordId,
|
||||
"id" => $field->id,
|
||||
"record_id" => $field->recordId,
|
||||
"field_id" => $field->fieldId,
|
||||
"locale" => $field->locale,
|
||||
"mode" => $field->mode->value,
|
||||
"value" => $field->value,
|
||||
|
||||
@@ -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"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php namespace Lucent\Core\Schema\Data\FieldProp;
|
||||
|
||||
class RelationFieldProp implements IFieldProp
|
||||
{
|
||||
public function __construct(
|
||||
public array $schemas,
|
||||
public int $min,
|
||||
public int $max,
|
||||
) {}
|
||||
}
|
||||
@@ -9,36 +9,5 @@ use Lucent\Query\Query;
|
||||
|
||||
class EdgeController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
public EdgeService $edgeService,
|
||||
public Query $query,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function insertMany(Request $request)
|
||||
{
|
||||
|
||||
$this->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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", [
|
||||
|
||||
+28
-26
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user