This commit is contained in:
2023-10-04 13:32:30 +03:00
parent 215d238505
commit 1ca5f4e521
82 changed files with 519 additions and 1889 deletions
-56
View File
@@ -1,56 +0,0 @@
<?php
namespace Lucent\AccessKey;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Validator;
use Lucent\LucentException;
use Lucent\Member\Role;
class AccessKey
{
public function __construct(
public readonly string $_id,
public readonly string $name,
public readonly Role $role,
public readonly string $token,
private readonly ?string $showOnceToken = null,
) {
$validator = Validator::make($this->toArray(), [
'name' => 'min:3,max:120',
]);
if ($validator->fails()) {
throw new LucentException($validator->errors()->first());
}
}
public static function fromArray(array $data): AccessKey
{
return new AccessKey(
_id: data_get($data, "_id"),
name: data_get($data, "name"),
role: Role::from(data_get($data, "role")),
token: data_get($data, "token"),
);
}
public function getShowOnceToken(): ?string
{
return $this->showOnceToken;
}
public function isValid(string $token): bool
{
return Hash::check($token, $this->token);
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
namespace Lucent\AccessKey;
use Carbon\Carbon;
use Lucent\Channel\ChannelContext;
use Lucent\DB\Monger;
class AccessKeyRepo
{
public static function findByToken(string $token): ?AccessKey
{
$channel = ChannelContext::get()->channel;
return $channel->accessKeys->firstWhere("token",$token);
}
public static function add(AccessKey $accessKey): void
{
$channel = ChannelContext::get()->channel;
Monger::central()->updateOne("channels", ["_id" => $channel->_id], [
'$push' => [
"accessKeys" => $accessKey->toArray()
],
'$set' => [
"updatedAt" => Carbon::now()->toJson(),
]
]);
}
public static function remove(string $id): void
{
$channel = ChannelContext::get()->channel;
Monger::central()->updateOne("channels", ["_id" => $channel->_id], [
'$pull' => [
"accessKeys" => ["_id" => $id]
],
'$set' => [
"updatedAt" => Carbon::now()->toJson(),
]
]);
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
namespace Lucent\AccessKey;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Lucent\Id\Id;
use Lucent\LucentException;
use Lucent\Member\Role;
class AccessKeyService
{
public static function create(string $name, string $role): AccessKey
{
$showOnceToken = Str::random(48);
$accessKey = new AccessKey(
_id: Id::new(),
name: $name,
token: hash("sha256", $showOnceToken),
role: Role::from($role),
showOnceToken: $showOnceToken,
);
AccessKeyRepo::add($accessKey);
return $accessKey;
}
public static function findByToken(string $token): ?AccessKey
{
$hashedToken = hash("sha256", $token);
return AccessKeyRepo::findByToken($hashedToken);
}
public static function remove(string $id): void
{
AccessKeyRepo::remove($id);
}
}
-41
View File
@@ -1,41 +0,0 @@
<?php
namespace Lucent\AccessKey;
use Illuminate\Support\Collection;
/**
* @extends \Illuminate\Support\Collection<int|string, AccessKey>
*/
final class AccessKeysCollection extends Collection
{
public function __construct(
AccessKey ...$array
) {
parent::__construct($array);
}
/**
* @return AccessKey[]
**/
public function toArray(): array
{
return collect($this)->values()->toArray();
}
public function toDB(): array
{
return \json_decode(\json_encode($this), true);
}
public static function fromArray(array $data): AccessKeysCollection
{
$item = array_map([AccessKey::class, 'fromArray'], $data);
return new AccessKeysCollection(...$item);
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
namespace Lucent\AccessKey;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Lucent\Account\Role;
class ApiMiddleware
{
public function handle(Request $request, Closure $next, string $accessLevel): Response
{
$bearerToken = $request->header('Authorization');
$token = str_replace('Bearer ', '', $bearerToken);
$role = match ($token) {
config("lucent.read_key") => Role::READER,
config("lucent.write_key") => Role::EDITOR,
config("lucent.developer_key") => Role::DEVELOPER,
default => ""
};
if (empty($role)) {
abort(401);
}
if (!$role->hasAccess($accessLevel)) {
abort(401);
}
$request->mergeIfMissing(['userId' => "system"]);
$request->mergeIfMissing(['userRole' => $role->value]);
return $next($request);
}
}
+6
View File
@@ -23,6 +23,12 @@ readonly class AccountService
}
public function countUsers(): int
{
return $this->userRepo->count();
}
/**
* @return Collection<UserProfile>
*/
+13
View File
@@ -154,5 +154,18 @@ readonly class AuthService
return $user;
}
/**
* @throws LucentException
*/
public function registerAdmin(
string $name,
string $email
): User
{
$user = $this->invite($name, $email, "admin");
$this->sendLoginEmail($user->email);
return $user;
}
}
+11 -5
View File
@@ -21,15 +21,21 @@ final class ChannelService
public static function fromConfig(): ChannelService
{
$configJson = file_get_contents(storage_path("lucent.config.json"));
$configArray = json_decode($configJson, true);
if(file_exists(storage_path("lucent.config.json"))){
$configJson = file_get_contents(storage_path("lucent.config.json"));
$configArray = json_decode($configJson, true);
}else{
$configArray = null;
}
$schemaService = new SchemaService();
$schemasCollection = (new Collection($configArray["schemas"]))->map([$schemaService, 'fromArray']);
$schemasCollection = (new Collection($configArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
$channel = new Channel(
name: $configArray["name"],
url: rtrim($configArray["url"], "/"),
name: $configArray["name"] ?? "",
url: rtrim($configArray["url"] ?? "", "/"),
schemas: $schemasCollection,
imageFilters: $configArray["imageFilters"] ?? [],
);
+5 -5
View File
@@ -8,7 +8,7 @@ use Illuminate\Console\Command;
class CompileConfig extends Command
{
protected $signature = 'lucent:compile:config {path}';
protected $signature = 'lucent:config';
protected $description = 'Compiles Config';
@@ -21,18 +21,18 @@ class CompileConfig extends Command
public function handle()
{
$configDir = base_path($this->argument('path'));
$configDir = base_path(config('lucent.config_path'));
$configJson = file_get_contents($configDir . "lucent.json");
$configJson = file_get_contents($configDir . "/lucent.json");
$config = json_decode($configJson, true);
$schemasDirIterator = new DirectoryIterator($configDir . "Schemas");
$schemasDirIterator = new DirectoryIterator($configDir . "/Schemas");
$schemas = [];
foreach ($schemasDirIterator as $file) {
if ($file->getExtension() !== "json") {
continue;
}
$schemaJson = file_get_contents($configDir . "Schemas/" . $file->getFilename());
$schemaJson = file_get_contents($configDir . "/Schemas/" . $file->getFilename());
$schema = json_decode($schemaJson, true);
if (empty($schema)) {
$this->error("Invalid JSON " . $file->getFilename());
+5 -1
View File
@@ -39,7 +39,11 @@ class RebuildThumbnails extends Command
$filesDir = storage_path("app/public/" . $schema->path . "/");
$thumbDir = storage_path("app/public/thumbs/" . $schema->path . "/");
if (!file_exists($thumbDir)) {
mkdir($thumbDir);
make_dir_r($thumbDir);
}
if (!file_exists($filesDir)) {
make_dir_r($filesDir);
}
$filesDirIterator = new DirectoryIterator($filesDir);
+1 -6
View File
@@ -1,11 +1,6 @@
<?php
return [
"view" => [
"image_server" => env("LUCENT_IMAGE_SERVER")
],
"read_key" => env("LUCENT_READ_KEY"),
"write_key" => env("LUCENT_WRITE_KEY"),
"developer_key" => env("LUCENT_DEVELOPER_KEY")
"config_path" => env("LUCENT_CONFIG_PATH", "app/Lucent")
];
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->uuid("id")->primary();
$table->string('name')->nullable();
$table->string('email')->unique();
$table->string('role');
$table->string('createdAt');
$table->string('updatedAt');
$table->string('loggedInAt');
$table->string('mailToken')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
};
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sessions');
}
};
@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('records', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('schema');
$table->string('status');
$table->json('data');
$table->json('_sys');
$table->json('_file');
$table->index(['schema', 'status']);
});
Schema::create('edges', function (Blueprint $table) {
$table->uuid('source');
$table->uuid('target');
$table->string('sourceSchema');
$table->string('targetSchema');
$table->string('field');
$table->string('rank');
$table->unique(['source', 'target', "field"]);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('records');
Schema::dropIfExists('edges');
}
};
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('revisions', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->uuid('recordId');
$table->string('schema');
$table->json('data');
$table->json('_sys');
$table->json('_file');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('revisions');
}
};
-73
View File
@@ -1,73 +0,0 @@
<?php
namespace Lucent\Field;
use Lucent\LucentException;
class Field
{
/**
* @param string[] $collections
*/
function __construct(
public FieldName $name,
public string $label,
public string $ui,
public string $help = "",
public bool $trashed = false,
public bool $locked = false,
public string $regex = "",
public string $mime = "",
public bool $readonly = false,
public bool $nullable = false,
public bool $required = false,
public int $decimals = 0,
public array $collections = [],
public mixed $min = null,
public mixed $max = null,
public mixed $default = null,
public string $optionsFrom = "",
public string $optionsField = "",
public bool $optionsSuggest = true,
public bool $unique = false, // not used
public string $layout = "",
) {
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
/**
* @throws LucentException
*/
public static function fromArray(array $data): Field
{
return new Field(
name: new FieldName($data["name"]),
label:$data["label"],
ui: $data["ui"],
help:$data["help"] ?? "",
trashed:$data["trashed"] ?? false,
locked:$data["locked"] ?? false,
regex:$data["regex"] ?? "" ,
mime:$data["mime"] ?? "" ,
readonly:$data["readonly"] ?? false,
nullable:$data["nullable"] ?? false,
required:$data["required"] ??false,
decimals:$data["decimals"] ?? 0,
collections:$data["collections"] ?? [],
min:$data["min"] ?? null,
max:$data["max"] ?? null,
default:$data["default"] ?? null,
optionsFrom:$data["optionsFrom"] ?? "" ,
optionsField:$data["optionsField"] ?? "" ,
optionsSuggest:$data["optionsSuggest"] ?? true,
unique:$data["unique"] ?? false,
layout:$data["layout"] ?? "" ,
);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
namespace Lucent\Field;
use Illuminate\Support\Collection;
/**
* @extends \Illuminate\Support\Collection<int|string, Field>
*/
final class FieldCollection extends Collection
{
public function __construct(
Field ...$array
) {
parent::__construct($array);
}
/**
* @return Schema[]
**/
public function toArray(): array
{
return collect($this)->values()->toArray();
}
public function findByName(string $name): ?Field
{
return $this->firstWhere("name", $name);
}
public static function fromArray(array $data): FieldCollection
{
$items = array_map([Field::class, 'fromArray'], $data);
return new FieldCollection(...$items);
}
public static function fromDB(string $data): FieldCollection
{
return FieldCollection::fromArray(\json_decode($data,true));
}
}
-167
View File
@@ -1,167 +0,0 @@
<?php
namespace Lucent\Field;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Channel\ChannelRepo;
use Lucent\LucentException;
use Lucent\Schema\Type as SchemaType;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
use function Lucent\Svelte\svelte;
class FieldController extends Controller
{
public function __construct(
private readonly FieldService $fieldService
)
{
}
public function new(Request $request)
{
$channel = ChannelRepo::current();
$schemaName = $request->route("schemaName");
$collections = collect($channel->schemas)->where("type", SchemaType::COLLECTION)->values()->toArray();
$filesSchemas = collect($channel->schemas)->where("type", SchemaType::FILES)->values()->toArray();
$newField = new Field(
name: new FieldName(""),
label: "",
ui: "text"
);
return svelte(
layout: "channel",
view: "fieldNew",
title: "New field",
data: [
"schemas" => $channel->schemas,
"schema" => $channel->schemas->where("name", $schemaName)->first(),
"collections" => $collections,
"filesSchemas" => $filesSchemas,
"field" => $newField->toArray(),
"uiList" => UI::values(),
]
);
}
public function create(Request $request)
{
try {
$this->fieldService->create(
schemaName: $request->route("schemaName"),
data: $request->input("data")
);
} catch (LucentException $th) {
return fail($th);
}
return ok();
}
public function edit(Request $request)
{
$channel = ChannelRepo::current();
$schemaName = $request->route("schemaName");
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$collections = collect($channel->schemas)->where("type", SchemaType::COLLECTION)->values()->toArray();
$filesSchemas = collect($channel->schemas)->where("type", SchemaType::FILES)->values()->toArray();
$field = collect($schema->fields)->where("name.value", $request->route("fid"))->first();
return svelte(
layout: "channel",
view: "fieldEdit",
title: "Edit field",
data: [
"schemas" => $channel->schemas,
"schema" => $schema,
"collections" => $collections,
"filesSchemas" => $filesSchemas,
"field" => $field,
"uiList" => UI::values(),
]
);
}
public function replace(Request $request)
{
$schema = $this->fieldService->replace(
schemaName: $request->route("schemaName"),
// fields:$request->input("fields")
fields: json_decode($request->input("fields"), true)
);
return ok($schema->toArray());
}
public function update(Request $request)
{
try {
$this->fieldService->update(
schemaName: $request->route("schemaName"),
data: $request->input("data")
);
} catch (LucentException $th) {
return fail($th);
}
return ok();
}
public function move(Request $request)
{
$newSchema = $this->fieldService->move(
schemaName: $request->route("schemaName"),
source: $request->input("source"),
target: $request->input("target") ?? "",
);
return ok($newSchema->toArray());
}
public function trash(Request $request)
{
try {
$newSchema = $this->fieldService->trash(
schemaName: $request->route("schemaName"),
fieldName: $request->input("field"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok($newSchema->toArray());
}
public function restore(Request $request)
{
try {
$newSchema = $this->fieldService->restore(
schemaName: $request->route("schemaName"),
fieldName: $request->input("field"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok($newSchema->toArray());
}
public function delete(Request $request)
{
try {
$newSchema = $this->fieldService->delete(
schemaName: $request->route("schemaName"),
fieldName: $request->input("field"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok($newSchema->toArray());
}
}
-42
View File
@@ -1,42 +0,0 @@
<?php
namespace Lucent\Field;
use JsonSerializable;
use Lucent\LucentException;
use Lucent\Validator\Validator;
class FieldName implements JsonSerializable
{
public string $value;
/**
* @throws LucentException
*/
function __construct(string $value)
{
Validator::single("Name", $value, "min:2|max:50|alpha_dash");
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
public function equals(FieldName $name): bool
{
return $this->value === $name->value;
}
public function __toString(): string
{
return $this->value;
}
public function jsonSerialize(): string
{
return $this->value;
}
}
-222
View File
@@ -1,222 +0,0 @@
<?php
namespace Lucent\Field;
use Lucent\Channel\ChannelRepo;
use Lucent\LucentException;
use Lucent\Primitive\Collection;
use Lucent\Schema\Schema;
use Lucent\Schema\SchemaRepo;
readonly class FieldService
{
public function __construct(
private SchemaRepo $schemaRepo
)
{
}
/**
* @throws LucentException
*/
public function create(
string $schemaName,
array $data
): Schema
{
if (empty($data["name"]) || empty($data["label"])) {
throw new LucentException("Name and Label are required");
}
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = Field::fromArray($data);
$this->validateNameUnique($schema, $field->name);
$schema->fields->push($field);
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function update(
string $schemaName,
array $data
): Schema
{
if (empty($data["name"]) || empty($data["label"])) {
throw new LucentException("Name and Label are required");
}
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name", $schemaName)->first();
$field = Field::fromArray($data);
if ($field->locked) {
throw new LucentException("Locked fields can't get updated");
}
$schema->fields = $schema->fields->map(function (Field $aField) use ($field) {
if (!$aField->name->equals($field->name)) {
return $aField;
}
return $field;
});
$this->schemaRepo->update($schema);
return $schema;
}
public function move(
string $schemaName,
string $source,
string $target,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name", $schemaName)->first();
if ($source === $target) {
return $schema;
}
$sourceField = $schema->fields->where("name", $source)->first();
$fieldsWithoutSource = $schema->fields
->where("name", "!=", $source)
->values()
->toArray();
if (empty($target)) {
$schema->fields = new Collection([$sourceField, ...$fieldsWithoutSource]);
} else {
$newFields = collect($fieldsWithoutSource)->reduce(function ($carry, $afield) use ($sourceField, $target) {
$carry[] = $afield;
if ($afield->name->value === $target) {
$carry[] = $sourceField;
}
return $carry;
}, []);
$schema->fields = new Collection($newFields);
}
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function trash(
string $schemaName,
string $fieldName,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = $schema->fields->where("name", $fieldName)->first();
if ($field->trashed) {
throw new LucentException("Field is already in trash");
}
if ($field->locked) {
throw new LucentException("Locked fields can't get trashed");
}
$schema->fields = $schema->fields->map(function (Field $aField) use ($fieldName) {
if ($aField->name->value !== $fieldName) {
return $aField;
}
$aField->trashed = true;
return $aField;
});
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function restore(
string $schemaName,
string $fieldName,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = $schema->fields->where("name", $fieldName)->first();
if (!$field->trashed) {
throw new LucentException("You can only restore trashed fields");
}
$schema->fields = $schema->fields->map(function (Field $aField) use ($fieldName) {
if ($aField->name->value !== $fieldName) {
return $aField;
}
$aField->trashed = false;
return $aField;
});
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
public function delete(
string $schemaName,
string $fieldName,
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name.value", $schemaName)->first();
$field = $schema->fields->where("name", $fieldName)->first();
if (!$field->trashed) {
throw new LucentException("You can only delete trashed fields");
}
$schema->fields = $schema->fields
->filter(fn(Field $aField) => $aField->name->value !== $fieldName)
->values();
$this->schemaRepo->update($schema);
return $schema;
}
public function replace(
string $schemaName,
array $fields
): Schema
{
$channel = ChannelRepo::current();
$schema = $channel->schemas->where("name", $schemaName)->first();
$schema->fields = new Collection();
$newFields = collect($fields)->map(function ($fieldData) use ($schema) {
$field = Field::fromArray($fieldData);
$this->validateNameUnique($schema, $field->name);
return $field;
})->toArray();
$schema->fields = new Collection($newFields);
$this->schemaRepo->update($schema);
return $schema;
}
/**
* @throws LucentException
*/
private function validateNameUnique(Schema $schema, string $name): void
{
$fieldExists = collect($schema->fields)->where("name.value", $name)->first();
if (!empty($fieldExists)) {
throw new LucentException("Field with name $name exists in schema $schema->label");
}
}
}
-298
View File
@@ -1,298 +0,0 @@
<?php
namespace Lucent\Field;
use Lucent\Schema\FieldType;
readonly class UI
{
function __construct(
public string $name,
public string $label,
public FieldType $type,
public bool $regex,
public bool $mime,
public bool $required,
public bool $nullable,
public bool $decimals,
public bool $collections,
public bool $min,
public bool $max,
public bool $default,
public bool $locked,
public bool $readonly,
public bool $options,
public bool $layout,
)
{
}
public static function getById(string $id): array
{
return self::buildUis()[$id];
}
public static function values(): array
{
return array_values(self::buildUis());
}
public static function buildUis(): array
{
return [
"uuid" => new UI(
name: "uuid",
label: "UUID",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: false,
locked: true,
readonly: true,
options: false,
layout: false,
),
"text" => new UI(
name: "text",
label: "Text",
type: FieldType::STRING,
regex: true,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"textarea" => new UI(
name: "textarea",
label: "Textarea",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"color" => new UI(
name: "color",
label: "Color",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"rich" => new UI(
name: "rich",
label: "Rich editor",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"block" => new UI(
name: "block",
label: "Block editor",
type: FieldType::JSON,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: true,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"file" => new UI(
name: "file",
label: "File",
type: FieldType::FILE,
regex: false,
mime: true,
required: false,
nullable: false,
decimals: false,
collections: true,
min: true,
max: true,
default: false,
locked: false,
readonly: false, // feature for later
options: false,
layout: false,
),
"reference" => new UI(
name: "reference",
label: "Reference",
type: FieldType::REFERENCE,
regex: false,
mime: false,
required: false,
nullable: false,
decimals: false,
collections: true,
min: true,
max: true,
default: false,
locked: false,
readonly: false, // feature for later
options: false,
layout: true,
),
"checkbox" => new UI(
name: "checkbox",
label: "Checkbox",
type: FieldType::BOOLEAN,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"number" => new UI(
name: "number",
label: "Number",
type: FieldType::NUMBER,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: true,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"date" => new UI(
name: "date",
label: "Date",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"datetime" => new UI(
name: "datetime",
label: "Datetime",
type: FieldType::STRING,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: true,
max: true,
default: true,
locked: true,
readonly: true,
options: true,
layout: false,
),
"json" => new UI(
name: "json",
label: "JSON",
type: FieldType::JSON,
regex: false,
mime: false,
required: true,
nullable: true,
decimals: false,
collections: false,
min: false,
max: false,
default: true,
locked: true,
readonly: true,
options: false,
layout: false,
),
"tab" => new UI(
name: "tab",
label: "Tab",
type: FieldType::TAB,
regex: false,
mime: false,
required: false,
nullable: false,
decimals: false,
collections: false,
min: false,
max: false,
default: false,
locked: true,
readonly: false,
options: false,
layout: false,
),
];
}
}
+10 -1
View File
@@ -3,18 +3,27 @@
namespace Lucent\File;
use Illuminate\Http\UploadedFile;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Record\File;
use Lucent\Record\QueryRecord;
use Lucent\Schema\Schema;
use Lucent\Schema\Type;
class FileService
{
public function __construct()
public function __construct(
public ChannelService $channelService
)
{
}
public function getPath(QueryRecord $file): string
{
return $this->channelService->channel->url. "/storage/".$file->_file->path;
}
/**
* @throws LucentException
*/
+11 -9
View File
@@ -102,7 +102,7 @@ function checkDuplicate(string $schemaName, string $checksum, int $filesize): st
{
$record = DB::table("records")
->where("_sys->schema", $schemaName)
->where("schema", $schemaName)
->where("_file->checksum", $checksum)
->where("_file->size", $filesize)
->first();
@@ -112,22 +112,24 @@ function checkDuplicate(string $schemaName, string $checksum, int $filesize): st
function createThumbnail(Filesystem $disk, string $schemaPath, string $filename, UploadedFile $file): void
{
$thumbDir = storage_path("app/public/thumbs/" . $schemaPath . "/");
if (!file_exists($thumbDir)) {
make_dir_r($thumbDir);
}
try {
$image = ImageManagerStatic::make($file);
} catch (Exception $e) {
logger($e->getMessage());
return;
}
$image->fit(300, 300);
try {
$image->encode('webp', 75);
$disk->put(
"thumbs/" . $schemaPath . "/" . $filename,
$image,
// 'public' // now managed by aws policy
);
$image->encode('webp', 75)->save($thumbDir .$filename);
} catch (Exception $e) {
logger($e->getMessage());
}
}
@@ -1,39 +0,0 @@
<?php
namespace Lucent\Http\Controller\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Channel\ChannelContext;
use Lucent\Schema\FieldRepo;
use Lucent\Schema\SchemaRepo;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
class FieldController extends Controller
{
public function create(Request $request)
{
try {
$schema = SchemaRepo::findByName($request->input("schema"));
FieldRepo::create($schema, $request->input("field"));
} catch (\Throwable $th) {
return fail($th);
}
return ok();
}
public function update(Request $request)
{
try {
$schema = SchemaRepo::findByName($request->input("schema"));
FieldRepo::update($schema, $request->input("field"));
} catch (\Throwable $th) {
return fail($th);
}
return ok();
}
}
+30 -33
View File
@@ -3,12 +3,14 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
use Lucent\Account\UserRepo;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Svelte\Svelte;
use function Lucent\Response\fail;
@@ -18,29 +20,28 @@ use function Lucent\Response\ok;
class AuthController extends Controller
{
public function __construct(
private readonly AuthService $authService,
private readonly Svelte $svelte,
private readonly UserRepo $userRepo,
private readonly AuthService $authService,
private readonly AccountService $accountService,
private readonly ChannelService $channelService,
private readonly Session $session,
private readonly Svelte $svelte,
)
{
}
public function register(Request $request): View
public function register(Request $request): View|RedirectResponse
{
$userCount = $this->userRepo->count();
$email = $request->input("email");
$token = $request->input("token");
if ($this->accountService->countUsers() > 0) {
return redirect($this->channelService->channel->lucentUrl . "/login");
}
return svelte(
return $this->svelte->render(
layout: "account",
view: "register",
title: "Create an account",
data: [
'email' => $email,
'token' => $token,
'userCount' => $userCount
]
);
}
@@ -48,23 +49,15 @@ class AuthController extends Controller
public function postRegister(Request $request): Response
{
if ($this->accountService->countUsers() > 0) {
abort(400);
}
try {
if ($request->input("isAdmin")) {
$this->authService->registerAdmin(
name: $request->input("name"),
password: $request->input("password"),
email: $request->input("email"),
);
} else {
$this->authService->register(
name: $request->input("name"),
password: $request->input("password"),
email: $request->input("email"),
token: $request->input("token") ?? "",
);
}
$this->authService->registerAdmin(
name: $request->input("name"),
email: $request->input("email"),
);
} catch (LucentException $th) {
return fail($th);
}
@@ -72,8 +65,12 @@ class AuthController extends Controller
return ok();
}
public function login(): View
public function login(): View|RedirectResponse
{
if ($this->accountService->countUsers() == 0) {
return redirect($this->channelService->channel->lucentUrl . "/register");
}
return $this->svelte->render(
layout: "account",
view: "login",
@@ -118,8 +115,8 @@ class AuthController extends Controller
public function logout(): RedirectResponse
{
session()->flush();
return redirect("/login");
$this->session->flush();
return redirect($this->channelService->channel->lucentUrl . "/login");
}
-15
View File
@@ -1,15 +0,0 @@
<?php
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Account\Auth;
use Lucent\Channel\ChannelContext;
use Lucent\Query\Options;
use Lucent\Query\Reference;
class EdgeController extends Controller
{
}
+2 -3
View File
@@ -121,9 +121,9 @@ class FileController extends Controller
})->toArray();
$queryResult = $this->query
$graph = $this->query
->filter([
"_sys.schema" => $schema->name
"schema" => $schema->name
])
->limit(15)
->skip(0)
@@ -132,7 +132,6 @@ class FileController extends Controller
->parentsDepth(0)
->run();
$graph = $queryResult->getQueryRecords();
return ok($graph->records->toArray());
}
}
-50
View File
@@ -1,50 +0,0 @@
<?php
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Schema\FolderRepo;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
class FolderController extends Controller
{
public function create(Request $request)
{
try {
$folder = FolderRepo::create($request->input("name"));
} catch (\Lucent\LucentException $th) {
return fail($th);
}
return ok((array)$folder);
}
public function update(Request $request, string $cid)
{
try {
$folder = FolderRepo::update(
id: $request->input("id"),
name: $request->input("name"),
);
} catch (\Lucent\LucentException $th) {
return fail($th);
}
return ok((array)$folder);
}
public function delete(Request $request, string $cid, string $folderid)
{
try {
FolderRepo::delete($folderid);
} catch (\Lucent\LucentException $th) {
return fail($th);
}
return redirect()->back();
}
}
+2 -6
View File
@@ -15,7 +15,6 @@ use function Lucent\Response\ok;
class HomeController extends Controller
{
public function __construct(
private readonly ChannelService $channelService,
private readonly Svelte $svelte,
private readonly UserRepo $userRepo,
private readonly Query $query,
@@ -45,7 +44,7 @@ class HomeController extends Controller
$limit = 30;
$queryResult = $this->query
$graph = $this->query
->filter($arguments)
->limit($limit)
->childrenDepth(1)
@@ -53,15 +52,12 @@ class HomeController extends Controller
->sort($sort)
->run();
$graph = $queryResult->getQueryRecords();
$users = $this->userRepo->all();
return ok([
"users" => $users,
"records" => $graph->getRootRecords()->toArray(),
"graph" => $graph->toArray(),
"graph" => toArray($graph),
"modalUrl" => $request->fullUrl(),
]);
}
+30 -39
View File
@@ -8,13 +8,13 @@ use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
use Lucent\Account\UserRepo;
use Lucent\Channel\ChannelService;
use Lucent\Field\System;
use Lucent\LucentException;
use Lucent\Query\Operator;
use Lucent\Query\Query;
use Lucent\Record\Manager;
use Lucent\Record\QueryRecord;
use Lucent\Record\RecordService;
use Lucent\Schema\System;
use Lucent\Schema\Validator\ValidatorException;
use Lucent\Svelte\Svelte;
use function Lucent\Response\fail;
@@ -39,13 +39,13 @@ class RecordController extends Controller
{
$schemaName = $request->route("schemaName");
$users = $this->accountService->all();
$schema = $this->channelService->channel->schemas->where("name", $schemaName)->first();
$schema = $this->channelService->getSchema($schemaName)->get();
$urlParams = $request->all();
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
$filter = data_get($urlParams, "filter") ?? [];
$arguments = array_merge([
"_sys.schema" => $schema->name,
"_sys.status_in" => "draft,published",
"schema" => $schema->name,
"status_in" => "draft,published",
], $filter);
@@ -53,38 +53,32 @@ class RecordController extends Controller
$limit = 15;
$records = [];
$graphArray = null;
$total = 0;
try {
$queryResult = $this->query
->filter($arguments)
->limit($limit)
->status(explode(",", $arguments["_sys.status_in"]))
->skip($skip)
->sort($sort)
->childrenDepth(1)
->parentsDepth(0)
->runWithCount();
$graph = $this->query
->filter($arguments)
->limit($limit)
->status(explode(",", $arguments["status_in"]))
->skip($skip)
->sort($sort)
->childrenDepth(1)
->parentsDepth(0)
->runWithCount();
$records = $graph->getRootRecords()->toArray();
$graph = $queryResult->getQueryRecords();
$graphArray = $graph->toArray();
$total = $queryResult->getTotal();
$records = $graph->getRootRecords()->toArray();
} catch (SubqueryNoResultException) {
}
$data = [
"schemas" => $this->channelService->channel->schemas,
"schema" => $schema,
"users" => $users,
"records" => $records,
"graph" => $graphArray,
"graph" => toArray($graph),
"systemFields" => array_values(System::list()),
"operators" => array_values(Operator::list()),
"sort" => $sort,
"limit" => $limit,
"skip" => $skip,
"total" => $total,
"total" => $graph->total ?? 0,
"filter" => $request->input("filter") ?? [],
"inModal" => true,
];
@@ -192,7 +186,7 @@ class RecordController extends Controller
$rid = $request->route("rid");
$queryResult = $this->query
$graph = $this->query
->filter(["id" => $rid])
->limit(1)
->skip(0)
@@ -203,9 +197,7 @@ class RecordController extends Controller
->run();
$graph = $queryResult->getQueryRecords();
if (empty($graph->records[0])) {
if ($graph->records->isEmpty()) {
return $this->svelte->render(
layout: "channel",
view: "recordNotFound",
@@ -213,8 +205,8 @@ class RecordController extends Controller
);
}
$record = $graph->records[0];
$schema = $this->channelService->channel->schemas->where("name", $record->_sys->schema)->first();
$record = $graph->records->first();
$schema = $this->channelService->getSchema($record->schema)->get();
$recordHistory = $this->recordManager->fromSession($request->session())->push($rid)->getRecords($rid);
$users = $this->userRepo->all();
return $this->svelte->render(
@@ -223,11 +215,10 @@ class RecordController extends Controller
title: "Edit Record",
data: [
"schema" => $schema,
"graph" => $graph->toArray(),
"record" => $record->toArray(),
"graph" => toArray($graph),
"record" => toArray($record),
"users" => $users,
"recordHistory" => $recordHistory,
"isCreateMode" => $record->_sys->status === "empty",
]
);
}
@@ -294,37 +285,37 @@ class RecordController extends Controller
if ($request->input("isCreateMode")) {
$this->recordService->create(
schemaName: $request->input("record._sys.schema"),
schemaName: $request->input("record.schema"),
data: $request->input("record.data"),
id: $request->input("record.id"),
file: $request->input("record._file") ?? [],
edges: $request->input("edges"),
status: $request->input("record._sys.status"),
status: $request->input("record.status"),
uploadFromUrl: ""
);
} else {
$this->recordService->update(
id: $request->input("record.id"),
data: $request->input("record.data"),
status: $request->input("record._sys.status"),
status: $request->input("record.status"),
edges: $request->input("edges"),
updateEdges: true,
);
}
$queryResult = $this->query
$newGraph = $this->query
->filter(["id" => $request->input("record.id")])
->limit(10)
->childrenDepth(2)
->parentsDepth(1)
->run();
$newGraph = $queryResult->getQueryRecords();
} catch (ValidatorException $th) {
return fail($th->getValidatorErrors());
} catch (LucentException $th) {
return fail($th);
}
return ok($newGraph->toArray());
return ok(toArray($newGraph));
}
-126
View File
@@ -1,126 +0,0 @@
<?php
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Lucent\Channel\ChannelRepo;
use Lucent\LucentException;
use Lucent\Schema\SchemaRepo;
use Lucent\Schema\SchemaService;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
use function Lucent\Svelte\svelte;
class SchemaController extends Controller
{
public function __construct(
private readonly SchemaRepo $schemaRepo,
private readonly SchemaService $schemaService
)
{
}
/**
* @throws LucentException
*/
public function new(): View
{
$schemas = $this->schemaRepo->all();
return svelte(
layout: "channel",
view: "schemaNew",
title: "Create schema",
data: [
"schemas" => $schemas,
"schema" => Schema::fromArray([]),
]
);
}
public function create(Request $request): Response
{
try {
$this->schemaService->create(
name: $request->input("name"),
label: $request->input("label"),
type: $request->input("type"),
isEntry: $request->input("isEntry"),
revisionRetentionDays: $request->input("revisionRetentionDays"),
revisionRetentionNumber: $request->input("revisionRetentionNumber"),
trashedRetentionDays: $request->input("trashedRetentionDays"),
fields: [],
path: $request->input("path"),
);
} catch (LucentException $th) {
return fail($th);
}
return ok();
}
public function edit(string $name): View
{
$channel = ChannelRepo::current();
return svelte(
layout: "channel",
view: "schemaEdit",
title: "Schemas",
data: [
"schemas" => $channel->schemas,
"schema" => $channel->schemas->where("name.value", $name)->first(),
]
);
}
public function delete(Request $request): View
{
$channel = ChannelRepo::current();
return svelte(
layout: "channel",
view: "schemaDelete",
title: "Schemas",
data: [
"channel" => $channel,
"schemas" => $channel->schemas,
"schema" => $channel->schemas->firstWhere("name", $request->route("name")),
"nav" => "collections",
]
);
}
public function update(Request $request): Response
{
try {
$schema = $this->schemaService->update(
name: $request->input("name"),
label: $request->input("label"),
isEntry: $request->input("isEntry"),
color: $request->input("color") ?? "",
visible: $request->input("visible") ?? [],
titleTemplate: $request->input("titleTemplate") ?? "",
revisionRetentionDays: $request->input("revisionRetentionDays"),
revisionRetentionNumber: $request->input("revisionRetentionNumber"),
trashedRetentionDays: $request->input("trashedRetentionDays"),
path: $request->input("path"),
);
} catch (LucentException $e) {
return fail($e);
}
return ok($schema->toArray());
}
public function postDelete(Request $request): Response
{
$this->schemaService->delete($request->input("name"));
return ok();
}
}
-66
View File
@@ -1,66 +0,0 @@
<?php
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Account\Auth;
use Lucent\Channel\ChannelContext;
use Lucent\Schema\SchemaRepo;
use Lucent\View\ViewRepo;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
class ViewController extends Controller
{
public function redirect(Request $request, string $cid, string $schemaName, string $viewName)
{
$schema = SchemaRepo::findByName($schemaName);
$view = $schema->views->whereName($viewName);
parse_str($view->params, $viewParamsArray);
return \redirect("/c/{$cid}/content/{$schemaName}/views/{$viewName}?{$view->params}");
}
public function create(Request $request)
{
try {
$schema = SchemaRepo::findByName($request->input("schemaName"));
ViewRepo::create($schema, $request->input("view"), Auth::currentUserId());
} catch (\Throwable $th) {
return fail($th);
}
return ok();
}
public function update(Request $request)
{
try {
$schema = SchemaRepo::findByName($request->input("schemaName"));
ViewRepo::update($schema, $request->input("view"));
} catch (\Throwable $th) {
return fail($th);
}
$schema = SchemaRepo::findByName($request->input("schemaName"));
$view = $schema->views->whereName($request->input("view.name"));
return ok((array)$view);
}
public function delete(Request $request, string $cid, string $schemaName, string $viewName)
{
try {
$schema = SchemaRepo::findByName($schemaName);
ViewRepo::delete($schema, $viewName);
} catch (\Throwable $th) {
return fail($th);
}
return redirect("c/{$cid}/content/{$schemaName}");
}
}
-29
View File
@@ -6,12 +6,6 @@ use Lucent\Http\Controller\Api\FileController;
use Lucent\Http\Controller\Api\RecordController;
use Lucent\Http\Controller\Api\SchemaController;
Route::middleware('auth.api:developer')->group(function () {
Route::post('/schemas', [SchemaController::class, 'create']);
Route::put('/schemas/', [SchemaController::class, 'update']);
Route::delete('/schemas/{name}', [SchemaController::class, 'delete']);
Route::post('/schemas/fields', [SchemaController::class, 'fields']);
});
Route::middleware('auth.api:editor')->group(function () {
@@ -29,26 +23,3 @@ Route::middleware('auth.api:reader')->group(function () {
Route::get('/records', [RecordController::class, 'records']);
});
// They need testing
// Route::middleware('auth.api')->prefix("/fields")->controller(FieldController::class)->group(function () {
// Route::post('/', 'create');
// Route::put('/', 'update');
// Route::delete('/{id}', 'delete');
// });
//Route::middleware(["auth.api"])->group(function () {
//
//// Route::get('/{rid}', 'findOne');
//
//// Route::delete('/records/{id}', [RecordController::class, 'delete']);
//// Route::post('/bulk', 'bulkCreate');
//// Route::put('/bulk', 'bulkUpdate');
//// Route::delete('/bulk', 'bulkDelete');
//
//
//});
//
//
//Route::middleware('auth.api')->prefix("/files")->controller(FileController::class)->group(function () {
// Route::post('/', 'upload');
//});
-13
View File
@@ -41,16 +41,6 @@ Route::group([
});
Route::middleware(["lucent.auth"])->prefix("/schemas/")->group(function () {
Route::get('/new/', [SchemaController::class, 'new']);
Route::post('/', [SchemaController::class, 'create']);
Route::get('/{name}/edit', [SchemaController::class, 'edit']);
Route::put('/', [SchemaController::class, 'update']);
Route::get('/{name}/delete', [SchemaController::class, 'delete']);
Route::post('/delete', [SchemaController::class, 'postDelete']);
});
Route::middleware(["lucent.auth"])->prefix("/records")->group(function () {
Route::get('/new', [RecordController::class, 'new']);
@@ -88,9 +78,6 @@ Route::group([
});
Route::get('/fs/cache/{path}', [FileController::class, 'get'])->where('path', '.*');
Route::get('/fs/{path}', [FileController::class, 'get'])->where('path', '.*');
});
+9
View File
@@ -5,9 +5,11 @@ namespace Lucent;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Intervention\Image\ImageManager;
use Lucent\Channel\ChannelService;
use Lucent\Commands\CompileConfig;
use Lucent\Commands\RebuildThumbnails;
use Lucent\File\FileService;
use Lucent\File\ImageService;
class LucentServiceProvider extends ServiceProvider
@@ -21,6 +23,11 @@ class LucentServiceProvider extends ServiceProvider
return ChannelService::fromConfig();
});
$this->app->bind(ImageManager::class, function () {
return new ImageManager(['driver' => 'imagick']);
});
}
/**
@@ -36,6 +43,7 @@ class LucentServiceProvider extends ServiceProvider
$this->loadRoutesFrom(__DIR__ . '/Http/web.php');
$this->loadRoutesFrom(__DIR__ . '/Http/api.php');
$this->loadMigrationsFrom(__DIR__ . '/Database/migrations');
if ($this->app->runningInConsole()) {
$this->commands([
@@ -45,6 +53,7 @@ class LucentServiceProvider extends ServiceProvider
}
View::share('image', app()->make(ImageService::class));
View::share('file', app()->make(FileService::class));
$this->publishes([
__DIR__ . '/Config/main.php' => config_path('lucent.php'),
+5 -9
View File
@@ -58,7 +58,7 @@ final class Query
if (!empty($edgesIds)) {
$edgeRecords = DB::table('records')
->whereIn("id", $edgesIds)
->whereIn("_sys->status", $this->options->status)
->whereIn("status", $this->options->status)
->get()->toArray();
}
$resultsRecordsUnique = collect(array_merge($resultsRecords, $edgeRecords))->unique("id")->values()->toArray();
@@ -76,7 +76,7 @@ final class Query
$queryRecords = collect($records)->map(function ($recordData) {
$record = Record::fromDB($recordData);
$record->data = $this->inputFormatter->fill($record->_sys->schema, $record->data);
$record->data = $this->inputFormatter->fill($record->schema, $record->data);
$queryRecord = QueryRecord::fromRecord($record);
$queryRecord->isRoot = data_get($recordData, "isRoot") === true;
return $queryRecord;
@@ -103,9 +103,7 @@ final class Query
}
/**
* @throws SubqueryNoResultException
*/
private function parseFilters(Builder $query): Builder
{
$filters = $this->filter->run(new Query($this->channelService, $this->inputFormatter));
@@ -148,7 +146,7 @@ final class Query
}
}
$query->whereIn("_sys->status", $this->options->status);
$query->whereIn("status", $this->options->status);
return $query;
}
@@ -211,9 +209,7 @@ final class Query
->get()->toArray();
}
/**
* @throws SubqueryNoResultException
*/
public
function runWithCount(): Graph
{
+2 -7
View File
@@ -3,9 +3,7 @@
namespace Lucent\Record;
use Illuminate\Contracts\Session\Session;
use Lucent\Primitive\Collection;
use Lucent\Query\Query;
use Lucent\Schema\Schema;
class Manager
{
@@ -79,19 +77,16 @@ class Manager
}, []);
}
/**
* @param Collection<Schema> $schemas
*/
public function getRecords(?string $ignoreId = null): array
{
$queryResult = $this->query
$graph = $this->query
->filter(["id_in" => $this->getIdsExcept($ignoreId)])
->limit(7)
->run();
$graph = $queryResult->getQueryRecords();
return $this->order($graph->records->toArray());
}
+4 -23
View File
@@ -9,6 +9,8 @@ class QueryRecord
function __construct(
public string $id,
public string $schema,
public Status $status,
public System $_sys,
public RecordData $data,
public bool $isRoot,
@@ -18,33 +20,12 @@ class QueryRecord
{
}
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,
schema: $record->schema,
status: $record->status,
_sys: $record->_sys,
data: $record->data,
isRoot: false,
+6 -18
View File
@@ -11,6 +11,8 @@ class Record implements JsonSerializable
function __construct(
public string $id,
public string $schema,
public Status $status,
public System $_sys,
public RecordData $data,
public ?File $_file = null,
@@ -18,15 +20,13 @@ class Record implements JsonSerializable
{
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
public function toDB(): array
{
return [
"id" => $this->id,
"status" => $this->status->value,
"schema" => $this->schema,
"_sys" => json_encode($this->_sys),
"_file" => json_encode($this->_file),
"data" => json_encode($this->data),
@@ -46,6 +46,8 @@ class Record implements JsonSerializable
return new Record(
id: $data->id,
schema: $data->schema,
status: Status::from($data->status),
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_file: $file,
@@ -58,20 +60,6 @@ class Record implements JsonSerializable
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,
);
}
}
-31
View File
@@ -1,31 +0,0 @@
<?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(),
);
}
}
+2 -2
View File
@@ -18,10 +18,10 @@ class RecordRepo
/**
* @param array<string> $ids
*/
public static function updateStatusBulk(RecordStatus $status, array $ids): void
public static function updateStatusBulk(Status $status, array $ids): void
{
DB::table("records")->whereIn("id", $ids)->update([
'_sys->status' => $status->value()
'status' => $status->value
]);
}
+22 -25
View File
@@ -60,11 +60,9 @@ readonly class RecordService
$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;
$edge["rank"] = $index;
return (array)(new Edge(...$edge));
})
->unique(fn($e) => $e['field'] . $e['source'] . $e['target'] . $e['sourceSchema'])
->values()->toArray();
@@ -77,7 +75,9 @@ readonly class RecordService
$record = new Record(
id: $id ?? Id::new(),
_sys: System::newRecord($schema, $this->authService->currentUserId(), $status),
schema: $schema->name,
status: Status::from($status),
_sys: System::newRecord($this->authService->currentUserId()),
data: $formattedData,
_file: $uploadResult->recordFile,
);
@@ -109,33 +109,34 @@ readonly class RecordService
): void
{
$queryResult = $this->query->filter(["id" => $id])->run();
$record = $queryResult->getQueryRecords()->records[0] ?? null;
$record = $this->query->filter(["id" => $id])->run()->records->first();
if (empty($record)) {
throw new LucentException("Record id is missing");
}
$formattedData = $this->inputFormatter->fill($record->_sys->schema, new RecordData($data));
$formattedData = $this->inputFormatter->fill($record->schema, new RecordData($data));
if ($updateEdges) {
$uniqueEdges = collect($edges)
->map(function ($edge, $index) {
$edge["rank"] = $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);
$errors = $this->recordValidator->check($record->schema, $formattedData, $uniqueEdgesCollection);
} else {
$errors = $this->recordValidator->check($record->_sys->schema, $formattedData, null);
$errors = $this->recordValidator->check($record->schema, $formattedData, null);
}
$newRecord = new Record(
id: $record->id,
_sys: $record->_sys->update($this->authService->currentUserId(), $status),
schema: $record->schema,
status: Status::from($status),
_sys: $record->_sys->update($this->authService->currentUserId()),
data: $record->data->merge($formattedData),
_file: $record->_file,
);
@@ -162,8 +163,7 @@ readonly class RecordService
array $recordsIds,
): void
{
$recordsStatus = (new RecordStatus($status));
RecordRepo::updateStatusBulk($recordsStatus, $recordsIds);
RecordRepo::updateStatusBulk(Status::from($status), $recordsIds);
}
/**
@@ -175,18 +175,17 @@ readonly class RecordService
string $recordId,
): string
{
$queryResult = $this->query
$graph = $this->query
->filter(["id" => $recordId])
->limit(1)
->childrenDepth(1)
->runWithCount();
->run();
$graph = $queryResult->getQueryRecords();
$record = $graph->records[0] ?? null;
$record = $graph->records->first();
if (empty($record)) {
throw new LucentException("Record id is missing");
}
$newRecordId = (string)Str::uuid();
$newEdgesData = $graph->edges
->filter(fn(Edge $edge) => $edge->source == $recordId)
@@ -199,7 +198,7 @@ readonly class RecordService
$record->id = $newRecordId;
return $this->create(
schemaName: $record->_sys->schema,
schemaName: $record->schema,
data: $record->data->toArray(),
id: $record->id,
file: $record->_file?->toArray() ?? [],
@@ -223,18 +222,14 @@ readonly class RecordService
* @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
);
}
@@ -254,7 +249,9 @@ readonly class RecordService
return new Record(
id: Id::new(),
_sys: System::newRecord($schema, $userId),
schema: $schema->name,
status: Status::DRAFT,
_sys: System::newRecord($userId),
data: $formattedData,
_file: null,
);
-31
View File
@@ -1,31 +0,0 @@
<?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;
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Lucent\Record;
enum Status: string
{
case DRAFT = "draft";
case PUBLISHED = "published";
case TRASHED = "trashed";
}
+8 -17
View File
@@ -5,18 +5,16 @@ namespace Lucent\Record;
use Carbon\Carbon;
use Lucent\Schema\Schema;
class System
readonly 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 int $version,
public string $createdBy,
public string $updatedBy,
public string $createdAt,
public string $updatedAt,
)
{
@@ -26,9 +24,7 @@ class System
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"),
@@ -37,13 +33,11 @@ class System
}
public static function newRecord(Schema $schema, string $userId, ?string $status = null): System
public static function newRecord(string $userId): System
{
$now = Carbon::now()->toJson();
return new System(
schema: $schema->name,
version: 1,
status: (new RecordStatus($status))->value(),
createdBy: $userId,
updatedBy: $userId,
createdAt: $now,
@@ -52,14 +46,11 @@ class System
}
public function update(string $userId, ?string $status = null): System
public function update(string $userId): 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,
+4 -6
View File
@@ -15,6 +15,7 @@ readonly class Revision
function __construct(
public string $id,
public string $recordId,
public string $schema,
public System $_sys,
public RecordData $data,
public ?File $_file = null,
@@ -22,12 +23,6 @@ readonly class Revision
{
}
public function toArray(): array
{
return json_decode(json_encode($this), true);
}
public static function fromDB(stdClass $data): Revision
{
@@ -42,6 +37,7 @@ readonly class Revision
return new Revision(
id: $data->id,
recordId: $data->recordId,
schema: $data->schema,
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_file: $file,
@@ -53,6 +49,7 @@ readonly class Revision
return [
"id" => $this->id,
"recordId" => $this->recordId,
"schema" => $this->schema,
"_sys" => json_encode($this->_sys),
"_file" => json_encode($this->_file),
"data" => json_encode($this->data),
@@ -65,6 +62,7 @@ readonly class Revision
return new Revision(
id: (string)Str::uuid(),
recordId: $record->id,
schema: $record->schema,
_sys: $record->_sys,
data: $record->data,
_file: $record->_file,
+2
View File
@@ -11,11 +11,13 @@ class CollectionSchema implements Schema
/**
* @param Collection<FieldInterface> $fields
* @param array<string> $visible
* @param array<string> $fields
*/
function __construct(
public string $name,
public string $label,
public array $visible,
public array $groups,
public Collection $fields,
public bool $isEntry = false,
public string $color = "",
+2
View File
@@ -11,12 +11,14 @@ class FilesSchema implements Schema
/**
* @param Collection<FieldInterface> $fields
* @param array<string> $groups
*/
function __construct(
public string $name,
public string $label,
public Collection $fields,
public string $path,
public array $groups,
public bool $isEntry = false,
public string $color = "",
public string $titleTemplate = "",
-31
View File
@@ -1,31 +0,0 @@
<?php
namespace Lucent\Schema;
use Lucent\Field\Field;
use Lucent\View\View;
/**
* @return string[]
**/
function visibleFields(Schema $schema, ?View $view = null): array
{
$visibleFieldNames = $schema->visible;
if (!empty($view)) {
$visibleFieldNames = $view->visible;
}
return $schema->fields
->filter(function (Field $f) use ($visibleFieldNames) {
if ($f->trashed || $f->ui == "tab") {
return false;
}
return $visibleFieldNames->contains($f->name->value);
})
->values()
->map(fn(Field $f) => $f->name->value)
->toArray();
}
+9 -7
View File
@@ -20,19 +20,21 @@ class SchemaService
name: $schemaArr["name"],
label: $schemaArr["label"],
visible: $schemaArr["visible"] ?? [],
groups: $schemaArr["groups"] ?? [],
fields: (new Collection($schemaArr["fields"]))->map([$this, 'mapFields']),
isEntry: $schemaArr["isEntry"],
color: $schemaArr["color"],
titleTemplate: $schemaArr["titleTemplate"],
isEntry: $schemaArr["isEntry"] ?? false,
color: $schemaArr["color"] ?? "",
titleTemplate: $schemaArr["titleTemplate"] ?? "",
),
"files" => new FilesSchema(
name: $schemaArr["name"],
label: $schemaArr["label"],
fields: (new Collection($schemaArr["fields"]))->map([$this, 'mapFields']),
path: $schemaArr["path"],
isEntry: $schemaArr["isEntry"],
color: $schemaArr["color"],
titleTemplate: $schemaArr["titleTemplate"]
path: $schemaArr["path"] ?? $schemaArr["name"],
groups: $schemaArr["groups"] ?? [],
isEntry: $schemaArr["isEntry"] ?? false,
color: $schemaArr["color"] ?? "",
titleTemplate: $schemaArr["titleTemplate"] ?? "",
)
};
@@ -1,6 +1,6 @@
<?php
namespace Lucent\Field;
namespace Lucent\Schema;
readonly class System
{
@@ -26,7 +26,7 @@ readonly class System
{
return [
"_sys.status" => new System(
name: "_sys.status",
name: "status",
label: "Status",
ui: "status",
files: false,
+2 -5
View File
@@ -14,7 +14,7 @@ class StaticGenerator
$filepath = public_path($path . "/index.html");
if (!file_exists(pathinfo($filepath, PATHINFO_DIRNAME))) {
$this->make_dir(pathinfo($filepath, PATHINFO_DIRNAME));
make_dir_r(pathinfo($filepath, PATHINFO_DIRNAME));
}
file_put_contents($filepath, $html);
@@ -35,8 +35,5 @@ class StaticGenerator
return 0;
}
private function make_dir(string $path): void
{
is_dir($path) || mkdir($path, 0777, true);
}
}
-13
View File
@@ -1,13 +0,0 @@
<?php
namespace Lucent;
class Support
{
public static function toArray(mixed $data):array
{
return \json_decode(\json_encode($data), true);
}
}
+4 -1
View File
@@ -6,7 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') - Lucent Data Platform</title>
@vite(['resources/js/app.js'])
@php
echo '<script type="module" crossorigin src="http://127.0.0.1:5173/@vite/client"></script>';
@endphp
<script type="module" crossorigin src="http://127.0.0.1:5173/main.js"></script>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
+8 -1
View File
@@ -6,7 +6,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') - Lucent Data Platform</title>
@vite(['resources/js/app.js'])
<!-- if development -->
@php
echo '<script type="module" crossorigin src="http://127.0.0.1:5173/@vite/client"></script>';
@endphp
<script type="module" crossorigin src="http://127.0.0.1:5173/main.js"></script>
<!-- if production -->
{{-- <link rel="stylesheet" href="/assets/{{ manifest['main.js'].css }}" />
<script type="module" src="/assets/{{ manifest['main.js'].file }}"></script> --}}
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
+6
View File
@@ -31,3 +31,9 @@ if (!function_exists('toArray')) {
}
if (!function_exists('make_dir_r')) {
function make_dir_r(string $path): void
{
is_dir($path) || mkdir($path, 0777, true);
}
}