boboko lulnar
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import RecordNotFound from "./records/NotFound.svelte";
|
import RecordNotFound from "./records/NotFound.svelte";
|
||||||
import RecordEdit from "./records/Edit.svelte";
|
import RecordEdit from "./records/Edit.svelte";
|
||||||
import ContentIndex from "./content/Index.svelte";
|
import ContentIndex from "./content/Index.svelte";
|
||||||
import {setContext} from "svelte";
|
import { setContext } from "svelte";
|
||||||
import Navbar from "./layout/Navbar.svelte";
|
import Navbar from "./layout/Navbar.svelte";
|
||||||
import HomeIndex from "./home/Index.svelte";
|
import HomeIndex from "./home/Index.svelte";
|
||||||
import BuildReport from "./build/Report.svelte";
|
import BuildReport from "./build/Report.svelte";
|
||||||
@@ -28,24 +28,21 @@
|
|||||||
export let axios;
|
export let axios;
|
||||||
export let readableSchemas;
|
export let readableSchemas;
|
||||||
|
|
||||||
|
|
||||||
setContext("axios", axios);
|
setContext("axios", axios);
|
||||||
setContext("channel", channel);
|
setContext("channel", channel);
|
||||||
setContext("readableSchemas", channel.schemas.filter((s) => readableSchemas.includes(s.name)));
|
setContext(
|
||||||
|
"readableSchemas",
|
||||||
|
channel.schemas.filter((s) => readableSchemas.includes(s.name)),
|
||||||
|
);
|
||||||
setContext("user", user);
|
setContext("user", user);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="main-wrapper">
|
<div class="main-wrapper">
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<Navbar schema={data.schema}/>
|
<Navbar schema={data.schema} />
|
||||||
|
</div>
|
||||||
|
<div class="main-content">
|
||||||
|
<Header />
|
||||||
|
<svelte:component this={components[view]} {title} {...data} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
|
||||||
<Header />
|
|
||||||
<svelte:component this={components[view]} {title} {...data}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { getContext, onMount } from "svelte";
|
||||||
import {getContext, onMount} from "svelte";
|
import RecordRow from "./RecordRow.svelte";
|
||||||
import RecordRow from "./RecordRow.svelte"
|
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
let records = [];
|
let records = [];
|
||||||
@@ -21,21 +20,17 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
|
<h3 class="header-small mb-4 mt-5">Latest Content changes</h3>
|
||||||
{#if records.length > 0}
|
{#if records.length > 0}
|
||||||
|
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<table class="">
|
<table class="">
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each records as record (record.id)}
|
{#each records as record (record.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<RecordRow {graph} {record} {users}/>
|
<RecordRow {graph} {record} {users} />
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
<script>
|
<script>
|
||||||
import Avatar from "../account/Avatar.svelte";
|
import Avatar from "../account/Avatar.svelte";
|
||||||
import {getContext} from "svelte";
|
import { getContext } from "svelte";
|
||||||
import Dropdown from "../common/Dropdown.svelte";
|
import Dropdown from "../common/Dropdown.svelte";
|
||||||
|
|
||||||
const channel = getContext("channel");
|
const channel = getContext("channel");
|
||||||
const user = getContext("user");
|
const user = getContext("user");
|
||||||
console.log( channel.commands)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="top-nav">
|
||||||
<div class="top-nav ">
|
|
||||||
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
|
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
|
||||||
|
|
||||||
{#if channel.commands.length > 0}
|
{#if channel.commands.length > 0}
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<div slot="button">Actions</div>
|
<div slot="button">Actions</div>
|
||||||
{#each channel.commands as command}
|
{#each channel.commands as command}
|
||||||
<a href="{channel.lucentUrl}/command-report/{command.signature}" class="top-nav-item">{command.name}</a>
|
<a
|
||||||
|
href="{channel.lucentUrl}/command-report/{command.signature}"
|
||||||
|
class="top-nav-item">{command.name}</a
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
<!-- <form method="GET">-->
|
<!-- <form method="GET">-->
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
<!-- </form>-->
|
<!-- </form>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<a href="{channel.lucentUrl}/profile">
|
<a href="{channel.lucentUrl}/profile">
|
||||||
<Avatar side="28" name={user.name}/>
|
<Avatar side="28" name={user.name} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from "../common/Icon.svelte"
|
import Icon from "../common/Icon.svelte";
|
||||||
|
|
||||||
export let step;
|
export let step;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="step step-{step.status}">
|
<div class="step step-{step.status}">
|
||||||
<div class="step-icon">
|
<div class="step-icon">
|
||||||
{#if step.status === "success"}
|
{#if step.status === "success"}
|
||||||
@@ -17,26 +15,24 @@
|
|||||||
<div style="width:100%">
|
<div style="width:100%">
|
||||||
<h4>{step.name}</h4>
|
<h4>{step.name}</h4>
|
||||||
<details>
|
<details>
|
||||||
<summary>Instuctions</summary>
|
<summary>Instructions</summary>
|
||||||
<code class="instructions">{step.instructions}</code>
|
<code class="instructions">{step.instructions}</code>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.step-success .step-icon{
|
.step-success .step-icon {
|
||||||
background: var(--suc10);
|
background: var(--suc10);
|
||||||
color: var(--suc100);
|
color: var(--suc100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-fail .step-icon{
|
.step-fail .step-icon {
|
||||||
background: var(--err10);
|
background: var(--err10);
|
||||||
color: var(--err100);
|
color: var(--err100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-icon{
|
.step-icon {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
@@ -64,4 +60,4 @@
|
|||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,15 +7,11 @@ use Lucent\Primitive\Collection;
|
|||||||
|
|
||||||
readonly class AccountService
|
readonly class AccountService
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private AuthService $authService,
|
private AuthService $authService,
|
||||||
private ChannelService $channelService,
|
private ChannelService $channelService,
|
||||||
private UserRepo $userRepo,
|
private UserRepo $userRepo,
|
||||||
)
|
) {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<User>
|
* @return Collection<User>
|
||||||
@@ -25,13 +21,11 @@ readonly class AccountService
|
|||||||
return $this->userRepo->all();
|
return $this->userRepo->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function countUsers(): int
|
public function countUsers(): int
|
||||||
{
|
{
|
||||||
return $this->userRepo->count();
|
return $this->userRepo->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<UserProfile>
|
* @return Collection<UserProfile>
|
||||||
*/
|
*/
|
||||||
@@ -54,6 +48,4 @@ readonly class AccountService
|
|||||||
$roles = $this->authService->currentUserRoles();
|
$roles = $this->authService->currentUserRoles();
|
||||||
return $this->channelService->schemasWritableByRoles($roles);
|
return $this->channelService->schemasWritableByRoles($roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-198
@@ -2,235 +2,52 @@
|
|||||||
|
|
||||||
namespace Lucent\Account;
|
namespace Lucent\Account;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Contracts\Session\Session;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lucent\Channel\ChannelService;
|
|
||||||
use Lucent\LucentException;
|
use Lucent\LucentException;
|
||||||
use Lucent\Mail\LoginMail;
|
|
||||||
|
|
||||||
readonly class AuthService
|
interface AuthService
|
||||||
{
|
{
|
||||||
|
public function currentUserId(): ?string;
|
||||||
|
|
||||||
public function __construct(
|
public function currentUserRoles(): array;
|
||||||
private ChannelService $channelService,
|
public function getCurrentUser(): User;
|
||||||
private UserRepo $userRepo,
|
|
||||||
public Session $session,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
public function isLoggedIn(): bool;
|
||||||
|
|
||||||
|
|
||||||
public function currentUserId(): ?string
|
|
||||||
{
|
|
||||||
|
|
||||||
if (app()->runningInConsole()) {
|
|
||||||
return config("lucent.systemUserId");
|
|
||||||
} elseif(request()->segment(1) !== "lucent") {
|
|
||||||
return config("lucent.systemUserId");
|
|
||||||
} else {
|
|
||||||
return $this->session->get("user.id");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
function currentUserRoles(): array
|
|
||||||
{
|
|
||||||
return $this->session->get("user.roles") ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
function isLoggedIn(): bool
|
|
||||||
{
|
|
||||||
return !empty($this->currentUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
public
|
public function login(string $email, string $token): void;
|
||||||
function login(string $email, string $token): void
|
|
||||||
{
|
|
||||||
|
|
||||||
$user = $this->userRepo->findByEmail(new Email($email));
|
public function refreshSession();
|
||||||
|
|
||||||
if ($user->isEmpty()) {
|
public function create(string $name, string $email, array $roles): User;
|
||||||
throw new LucentException("Your account was not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user->get()->isRemoved()) {
|
|
||||||
throw new LucentException("Your account is not active");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user->get()->mailToken !== $token) {
|
|
||||||
throw new LucentException("Token has expired or is invalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Carbon::parse($user->get()->loggedInAt)->lte(Carbon::now()->subHours(1))) {
|
|
||||||
throw new LucentException("Token has expired.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$newUser = $user->get();
|
|
||||||
$newUser->updatedAt = Carbon::now()->toJson();
|
|
||||||
$newUser->mailToken = null;
|
|
||||||
$this->userRepo->update($newUser);
|
|
||||||
$this->session->put(["user" => $user->get()->safe()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
function refreshSession()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
$user = $this->userRepo->findById($this->currentUserId());
|
|
||||||
|
|
||||||
if ($user->isEmpty()) {
|
|
||||||
throw new LucentException("Your account was not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user->get()->isRemoved()) {
|
|
||||||
throw new LucentException("Your account is not active");
|
|
||||||
}
|
|
||||||
|
|
||||||
$newUser = $user->get();
|
|
||||||
$this->session->put(["user" => $user->get()->safe()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
function create(string $name, string $email, array $roles): User
|
|
||||||
{
|
|
||||||
$user = new User(
|
|
||||||
id: (string)Str::uuid(),
|
|
||||||
name: new Name($name),
|
|
||||||
email: new Email($email),
|
|
||||||
roles: $this->validateRoles($roles),
|
|
||||||
createdAt: Carbon::now()->toJson(),
|
|
||||||
updatedAt: Carbon::now()->toJson(),
|
|
||||||
loggedInAt: Carbon::now()->toJson(),
|
|
||||||
mailToken: Token::new(32),
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->userRepo->insert($user);
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function sendLoginEmail(string $email): void
|
|
||||||
{
|
|
||||||
$emailAddress = (new Email($email));
|
|
||||||
$user = $this->userRepo->findByEmail($emailAddress);
|
|
||||||
|
|
||||||
if ($user->isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user->get()->isRemoved()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$newToken = $this->userRepo->updateLoginToken($user->get()->id);
|
|
||||||
|
|
||||||
Mail::to($email)->send(
|
|
||||||
new LoginMail(
|
|
||||||
$email,
|
|
||||||
$newToken,
|
|
||||||
$this->channelService->channel->lucentUrl
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public function sendLoginEmail(string $email): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
public
|
public function changeRoles(string $userId, array $roles): void;
|
||||||
function changeRoles(string $userId, array $roles): void
|
|
||||||
{
|
|
||||||
$user = $this->userRepo->findById($userId);
|
|
||||||
|
|
||||||
if ($user->isEmpty()) {
|
|
||||||
throw new LucentException("User not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
$newUser = $user->get();
|
|
||||||
$newUser->roles = $this->validateRoles($roles);
|
|
||||||
$newUser->updatedAt = Carbon::now()->toJson();
|
|
||||||
$this->userRepo->update($newUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
public
|
public function updateName(string $name): void;
|
||||||
function updateName(string $name): void
|
|
||||||
{
|
|
||||||
$name = (new Name($name));
|
|
||||||
$this->userRepo->updateName($this->currentUserId(), $name);
|
|
||||||
$user = $this->userRepo->findById($this->currentUserId());
|
|
||||||
$this->session->put(["user" => $user->get()->safe()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
public
|
public function updateEmail(string $email): void;
|
||||||
function updateEmail(string $email): void
|
|
||||||
{
|
|
||||||
$email = (new Email($email));
|
|
||||||
$user = $this->userRepo->findByEmail($email);
|
|
||||||
if ($user->isDefined()) {
|
|
||||||
throw new LucentException("Email already assigned to user");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->userRepo->updateEmail($this->currentUserId(), $email);
|
|
||||||
$user = $this->userRepo->findById($this->currentUserId());
|
|
||||||
$this->session->put(["user" => $user->get()->safe()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
public
|
public function invite(string $name, string $email, array $roles): User;
|
||||||
function invite(
|
|
||||||
string $name,
|
|
||||||
string $email,
|
|
||||||
array $roles
|
|
||||||
): User
|
|
||||||
{
|
|
||||||
$user = $this->create($name, $email, $roles);
|
|
||||||
$this->sendLoginEmail($user->email);
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
*/
|
*/
|
||||||
public
|
public function registerAdmin(string $name, string $email): User;
|
||||||
function registerAdmin(
|
|
||||||
string $name,
|
|
||||||
string $email
|
|
||||||
): User
|
|
||||||
{
|
|
||||||
$user = $this->invite($name, $email, ["admin"]);
|
|
||||||
$this->sendLoginEmail($user->email);
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
function validateRoles(array $roles): array
|
|
||||||
{
|
|
||||||
return collect($roles)
|
|
||||||
->filter(fn(string $role) => in_array($role, $this->channelService->channel->roles))
|
|
||||||
->unique()
|
|
||||||
->values()
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
public function validateRoles(array $roles): array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Account;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Contracts\Session\Session;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Lucent\Channel\ChannelService;
|
||||||
|
use Lucent\LucentException;
|
||||||
|
use Lucent\Mail\LoginMail;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
|
|
||||||
|
readonly class AuthServiceLucent implements AuthService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ChannelService $channelService,
|
||||||
|
private UserRepo $userRepo,
|
||||||
|
public Session $session,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function currentUserId(): ?string
|
||||||
|
{
|
||||||
|
if (app()->runningInConsole()) {
|
||||||
|
return config("lucent.systemUserId");
|
||||||
|
} elseif (request()->segment(1) !== "lucent") {
|
||||||
|
return config("lucent.systemUserId");
|
||||||
|
}
|
||||||
|
return $this->session->get("user.id");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function currentUserRoles(): array
|
||||||
|
{
|
||||||
|
return $this->session->get("user.roles") ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentUser(): User
|
||||||
|
{
|
||||||
|
return new User(
|
||||||
|
id: $this->session->get("user.id"),
|
||||||
|
name: new Name($this->session->get("user.name")),
|
||||||
|
email: new Email($this->session->get("user.email")),
|
||||||
|
roles: $this->session->get("user.roles"),
|
||||||
|
createdAt: $this->session->get("user.createdAt"),
|
||||||
|
updatedAt: $this->session->get("user.updatedAt"),
|
||||||
|
loggedInAt: null,
|
||||||
|
mailToken: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLoggedIn(): bool
|
||||||
|
{
|
||||||
|
return !empty($this->currentUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function login(string $email, string $token): void
|
||||||
|
{
|
||||||
|
$user = $this->userRepo->findByEmail(new Email($email));
|
||||||
|
|
||||||
|
if ($user->isEmpty()) {
|
||||||
|
throw new LucentException("Your account was not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->get()->isRemoved()) {
|
||||||
|
throw new LucentException("Your account is not active");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->get()->mailToken !== $token) {
|
||||||
|
throw new LucentException("Token has expired or is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Carbon::parse($user->get()->loggedInAt)->lte(
|
||||||
|
Carbon::now()->subHours(1),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new LucentException("Token has expired.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$newUser = $user->get();
|
||||||
|
$newUser->updatedAt = Carbon::now()->toJson();
|
||||||
|
$newUser->mailToken = null;
|
||||||
|
$this->userRepo->update($newUser);
|
||||||
|
$this->session->put(["user" => $user->get()->safe()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshSession()
|
||||||
|
{
|
||||||
|
$user = $this->userRepo->findById($this->currentUserId());
|
||||||
|
|
||||||
|
if ($user->isEmpty()) {
|
||||||
|
throw new LucentException("Your account was not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->get()->isRemoved()) {
|
||||||
|
throw new LucentException("Your account is not active");
|
||||||
|
}
|
||||||
|
|
||||||
|
$newUser = $user->get();
|
||||||
|
$this->session->put(["user" => $user->get()->safe()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(string $name, string $email, array $roles): User
|
||||||
|
{
|
||||||
|
$user = new User(
|
||||||
|
id: (string) Str::uuid(),
|
||||||
|
name: new Name($name),
|
||||||
|
email: new Email($email),
|
||||||
|
roles: $this->validateRoles($roles),
|
||||||
|
createdAt: Carbon::now()->toJson(),
|
||||||
|
updatedAt: Carbon::now()->toJson(),
|
||||||
|
loggedInAt: Carbon::now()->toJson(),
|
||||||
|
mailToken: Token::new(32),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->userRepo->insert($user);
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendLoginEmail(string $email): void
|
||||||
|
{
|
||||||
|
$emailAddress = new Email($email);
|
||||||
|
$user = $this->userRepo->findByEmail($emailAddress);
|
||||||
|
|
||||||
|
if ($user->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->get()->isRemoved()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newToken = $this->userRepo->updateLoginToken($user->get()->id);
|
||||||
|
|
||||||
|
Mail::to($email)->send(
|
||||||
|
new LoginMail(
|
||||||
|
$email,
|
||||||
|
$newToken,
|
||||||
|
$this->channelService->channel->lucentUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function changeRoles(string $userId, array $roles): void
|
||||||
|
{
|
||||||
|
$user = $this->userRepo->findById($userId);
|
||||||
|
|
||||||
|
if ($user->isEmpty()) {
|
||||||
|
throw new LucentException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
$newUser = $user->get();
|
||||||
|
$newUser->roles = $this->validateRoles($roles);
|
||||||
|
$newUser->updatedAt = Carbon::now()->toJson();
|
||||||
|
$this->userRepo->update($newUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function updateName(string $name): void
|
||||||
|
{
|
||||||
|
$name = new Name($name);
|
||||||
|
$this->userRepo->updateName($this->currentUserId(), $name);
|
||||||
|
$user = $this->userRepo->findById($this->currentUserId());
|
||||||
|
$this->session->put(["user" => $user->get()->safe()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function updateEmail(string $email): void
|
||||||
|
{
|
||||||
|
$email = new Email($email);
|
||||||
|
$user = $this->userRepo->findByEmail($email);
|
||||||
|
if ($user->isDefined()) {
|
||||||
|
throw new LucentException("Email already assigned to user");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->userRepo->updateEmail($this->currentUserId(), $email);
|
||||||
|
$user = $this->userRepo->findById($this->currentUserId());
|
||||||
|
$this->session->put(["user" => $user->get()->safe()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function invite(string $name, string $email, array $roles): User
|
||||||
|
{
|
||||||
|
$user = $this->create($name, $email, $roles);
|
||||||
|
$this->sendLoginEmail($user->email);
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function registerAdmin(string $name, string $email): User
|
||||||
|
{
|
||||||
|
$user = $this->invite($name, $email, ["admin"]);
|
||||||
|
$this->sendLoginEmail($user->email);
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateRoles(array $roles): array
|
||||||
|
{
|
||||||
|
return collect($roles)
|
||||||
|
->filter(
|
||||||
|
fn(string $role) => in_array(
|
||||||
|
$role,
|
||||||
|
$this->channelService->channel->roles,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Account;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Session\Session;
|
||||||
|
use Lucent\Channel\ChannelService;
|
||||||
|
use Lucent\LucentException;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
|
|
||||||
|
readonly class AuthServiceLunar implements AuthService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ChannelService $channelService,
|
||||||
|
private UserRepo $userRepo,
|
||||||
|
public Session $session,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function currentUserId(): ?string
|
||||||
|
{
|
||||||
|
if (app()->runningInConsole()) {
|
||||||
|
return config("lucent.systemUserId");
|
||||||
|
} elseif (request()->segment(1) !== "lucent") {
|
||||||
|
return config("lucent.systemUserId");
|
||||||
|
}
|
||||||
|
return Filament::auth()->user()->id;
|
||||||
|
}
|
||||||
|
public function getCurrentUser(): User
|
||||||
|
{
|
||||||
|
return new User(
|
||||||
|
id: Filament::auth()->user()->id,
|
||||||
|
name: new Name(
|
||||||
|
Filament::auth()->user()->first_name .
|
||||||
|
" " .
|
||||||
|
Filament::auth()->user()->last_name,
|
||||||
|
),
|
||||||
|
email: new Email(Filament::auth()->user()->email),
|
||||||
|
roles: [],
|
||||||
|
createdAt: Filament::auth()->user()->created_at,
|
||||||
|
updatedAt: Filament::auth()->user()->updated_at,
|
||||||
|
loggedInAt: null,
|
||||||
|
mailToken: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function currentUserRoles(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLoggedIn(): bool
|
||||||
|
{
|
||||||
|
return !empty($this->currentUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function login(string $email, string $token): void {}
|
||||||
|
|
||||||
|
public function refreshSession() {}
|
||||||
|
|
||||||
|
public function create(string $name, string $email, array $roles): User {}
|
||||||
|
|
||||||
|
public function sendLoginEmail(string $email): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function changeRoles(string $userId, array $roles): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function updateName(string $name): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function updateEmail(string $email): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function invite(string $name, string $email, array $roles): User {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws LucentException
|
||||||
|
*/
|
||||||
|
public function registerAdmin(string $name, string $email): User {}
|
||||||
|
|
||||||
|
public function validateRoles(array $roles): array
|
||||||
|
{
|
||||||
|
return collect($roles)
|
||||||
|
->filter(
|
||||||
|
fn(string $role) => in_array(
|
||||||
|
$role,
|
||||||
|
$this->channelService->channel->roles,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-85
@@ -2,111 +2,37 @@
|
|||||||
|
|
||||||
namespace Lucent\Account;
|
namespace Lucent\Account;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Lucent\Database\Database;
|
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Primitive\Collection;
|
||||||
use PhpOption\Option;
|
use PhpOption\Option;
|
||||||
|
|
||||||
class UserRepo
|
interface UserRepo
|
||||||
{
|
{
|
||||||
|
public function count(): int;
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return Database::make()->table("users")->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<User>
|
* @return Collection<User>
|
||||||
*/
|
*/
|
||||||
public function all(): Collection
|
public function all(): Collection;
|
||||||
{
|
|
||||||
$usersData = Database::make()->table("users")->get();
|
|
||||||
|
|
||||||
$users = array_map(fn($userData) => $this->fromArray((array)$userData), $usersData->toArray());
|
public static function insert(User $user): void;
|
||||||
return new Collection($users);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public function update(User $user): void;
|
||||||
|
|
||||||
public static function insert(User $user): void
|
public function updateLoginToken(string $id): string;
|
||||||
{
|
|
||||||
$userData = toArray($user);
|
|
||||||
$userData["roles"] = json_encode($userData["roles"]);
|
|
||||||
Database::make()->table("users")->insert($userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(User $user): void
|
|
||||||
{
|
|
||||||
$userData = toArray($user);
|
|
||||||
$userData["roles"] = json_encode($userData["roles"]);
|
|
||||||
Database::make()->table("users")->where("id", $user->id)->update($userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function updateLoginToken(string $id): string
|
|
||||||
{
|
|
||||||
$newToken = Token::new(32);
|
|
||||||
|
|
||||||
Database::make()->table("users")
|
|
||||||
->where("id", $id)
|
|
||||||
->update([
|
|
||||||
'loggedInAt' => Carbon::now()->toJson(),
|
|
||||||
'mailToken' => $newToken,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $newToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Option<User>
|
* @return Option<User>
|
||||||
*/
|
*/
|
||||||
public function findByEmail(Email $email): Option
|
public function findByEmail(Email $email): Option;
|
||||||
{
|
|
||||||
$user = Database::make()->table("users")->where("email", $email->value())->first();
|
|
||||||
|
|
||||||
if (empty($user)) {
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
|
|
||||||
return some($this->fromArray((array)$user));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Option<User>
|
* @return Option<User>
|
||||||
*/
|
*/
|
||||||
public function findById(string $id): Option
|
public function findById(string $id): Option;
|
||||||
{
|
|
||||||
$user = Database::make()->table("users")->where("id", $id)->first();
|
|
||||||
|
|
||||||
if (empty($user)) {
|
public function updateName(string $userId, Name $name): void;
|
||||||
return none();
|
|
||||||
}
|
|
||||||
|
|
||||||
return some($this->fromArray((array)$user));
|
public function updateEmail(string $userId, Email $email): void;
|
||||||
}
|
|
||||||
|
|
||||||
|
public function fromArray(array $data): User;
|
||||||
public function updateName(string $userId, Name $name): void
|
|
||||||
{
|
|
||||||
Database::make()->table("users")->where("id", $userId)->update(["name" => $name->value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateEmail(string $userId, Email $email): void
|
|
||||||
{
|
|
||||||
Database::make()->table("users")->where("id", $userId)->update(["email" => $email->value()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fromArray(array $data): User
|
|
||||||
{
|
|
||||||
return new User(
|
|
||||||
id: $data["id"],
|
|
||||||
name: new Name($data["name"] ?? ""),
|
|
||||||
email: new Email($data["email"]),
|
|
||||||
roles: json_decode($data["roles"] ?? "[]", true),
|
|
||||||
createdAt: $data["createdAt"],
|
|
||||||
updatedAt: $data["updatedAt"],
|
|
||||||
loggedInAt: $data["loggedInAt"] ?? null,
|
|
||||||
mailToken: $data["mailToken"] ?? null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Account;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Lucent\Database\Database;
|
||||||
|
use Lucent\Primitive\Collection;
|
||||||
|
use PhpOption\Option;
|
||||||
|
|
||||||
|
class UserRepoLucent implements UserRepo
|
||||||
|
{
|
||||||
|
private $tableName = "lucent_users";
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return Database::make()->table($this->tableName)->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<User>
|
||||||
|
*/
|
||||||
|
public function all(): Collection
|
||||||
|
{
|
||||||
|
$usersData = Database::make()->table($this->tableName)->get();
|
||||||
|
|
||||||
|
$users = array_map(
|
||||||
|
fn($userData) => $this->fromArray((array) $userData),
|
||||||
|
$usersData->toArray(),
|
||||||
|
);
|
||||||
|
return new Collection($users);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function insert(User $user): void
|
||||||
|
{
|
||||||
|
$userData = toArray($user);
|
||||||
|
$userData["roles"] = json_encode($userData["roles"]);
|
||||||
|
Database::make()->table($this->tableName)->insert($userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user): void
|
||||||
|
{
|
||||||
|
$userData = toArray($user);
|
||||||
|
$userData["roles"] = json_encode($userData["roles"]);
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $user->id)
|
||||||
|
->update($userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateLoginToken(string $id): string
|
||||||
|
{
|
||||||
|
$newToken = Token::new(32);
|
||||||
|
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $id)
|
||||||
|
->update([
|
||||||
|
"loggedInAt" => Carbon::now()->toJson(),
|
||||||
|
"mailToken" => $newToken,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Option<User>
|
||||||
|
*/
|
||||||
|
public function findByEmail(Email $email): Option
|
||||||
|
{
|
||||||
|
$user = Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("email", $email->value())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (empty($user)) {
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
|
||||||
|
return some($this->fromArray((array) $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Option<User>
|
||||||
|
*/
|
||||||
|
public function findById(string $id): Option
|
||||||
|
{
|
||||||
|
$user = Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (empty($user)) {
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
|
||||||
|
return some($this->fromArray((array) $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateName(string $userId, Name $name): void
|
||||||
|
{
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $userId)
|
||||||
|
->update(["name" => $name->value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateEmail(string $userId, Email $email): void
|
||||||
|
{
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $userId)
|
||||||
|
->update(["email" => $email->value()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromArray(array $data): User
|
||||||
|
{
|
||||||
|
return new User(
|
||||||
|
id: $data["id"],
|
||||||
|
name: new Name($data["name"] ?? ""),
|
||||||
|
email: new Email($data["email"]),
|
||||||
|
roles: json_decode($data["roles"] ?? "[]", true),
|
||||||
|
createdAt: $data["createdAt"],
|
||||||
|
updatedAt: $data["updatedAt"],
|
||||||
|
loggedInAt: $data["loggedInAt"] ?? null,
|
||||||
|
mailToken: $data["mailToken"] ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Account;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Lucent\Database\Database;
|
||||||
|
use Lucent\Primitive\Collection;
|
||||||
|
use PhpOption\Option;
|
||||||
|
|
||||||
|
class UserRepoLunar implements UserRepo
|
||||||
|
{
|
||||||
|
private $tableName = "lunar_staff";
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return Database::make()->table($this->tableName)->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<User>
|
||||||
|
*/
|
||||||
|
public function all(): Collection
|
||||||
|
{
|
||||||
|
$usersData = Database::make()->table($this->tableName)->get();
|
||||||
|
|
||||||
|
$users = array_map(
|
||||||
|
fn($userData) => $this->fromArray((array) $userData),
|
||||||
|
$usersData->toArray(),
|
||||||
|
);
|
||||||
|
return new Collection($users);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function insert(User $user): void
|
||||||
|
{
|
||||||
|
$userData = toArray($user);
|
||||||
|
$userData["roles"] = json_encode($userData["roles"]);
|
||||||
|
Database::make()->table($this->tableName)->insert($userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user): void
|
||||||
|
{
|
||||||
|
$userData = toArray($user);
|
||||||
|
$userData["roles"] = json_encode($userData["roles"]);
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $user->id)
|
||||||
|
->update($userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateLoginToken(string $id): string
|
||||||
|
{
|
||||||
|
$newToken = Token::new(32);
|
||||||
|
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $id)
|
||||||
|
->update([
|
||||||
|
"loggedInAt" => Carbon::now()->toJson(),
|
||||||
|
"mailToken" => $newToken,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Option<User>
|
||||||
|
*/
|
||||||
|
public function findByEmail(Email $email): Option
|
||||||
|
{
|
||||||
|
$user = Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("email", $email->value())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (empty($user)) {
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
|
||||||
|
return some($this->fromArray((array) $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Option<User>
|
||||||
|
*/
|
||||||
|
public function findById(string $id): Option
|
||||||
|
{
|
||||||
|
$user = Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (empty($user)) {
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
|
||||||
|
return some($this->fromArray((array) $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateName(string $userId, Name $name): void
|
||||||
|
{
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $userId)
|
||||||
|
->update(["name" => $name->value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateEmail(string $userId, Email $email): void
|
||||||
|
{
|
||||||
|
Database::make()
|
||||||
|
->table($this->tableName)
|
||||||
|
->where("id", $userId)
|
||||||
|
->update(["email" => $email->value()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromArray(array $data): User
|
||||||
|
{
|
||||||
|
return new User(
|
||||||
|
id: $data["id"],
|
||||||
|
name: new Name(
|
||||||
|
$data["first_name"] . " " . $data["last_name"] ?? "",
|
||||||
|
),
|
||||||
|
email: new Email($data["email"]),
|
||||||
|
roles: [],
|
||||||
|
createdAt: $data["created_at"],
|
||||||
|
updatedAt: $data["updated_at"],
|
||||||
|
loggedInAt: null,
|
||||||
|
mailToken: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace Lucent\Channel;
|
namespace Lucent\Channel;
|
||||||
|
|
||||||
|
|
||||||
use Lucent\Channel\Data\UserCommand;
|
use Lucent\Channel\Data\UserCommand;
|
||||||
use Lucent\Primitive\Collection;
|
use Lucent\Primitive\Collection;
|
||||||
use Lucent\Schema\Schema;
|
use Lucent\Schema\Schema;
|
||||||
@@ -13,12 +12,7 @@ final class ChannelService
|
|||||||
{
|
{
|
||||||
public Channel $channel;
|
public Channel $channel;
|
||||||
|
|
||||||
private function __construct(
|
private function __construct(public SchemaService $schemaService) {}
|
||||||
public SchemaService $schemaService,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromConfig(): ChannelService
|
public static function fromConfig(): ChannelService
|
||||||
{
|
{
|
||||||
@@ -28,7 +22,10 @@ final class ChannelService
|
|||||||
$schemasArray = json_decode($schemasJson, true);
|
$schemasArray = json_decode($schemasJson, true);
|
||||||
}
|
}
|
||||||
$schemaService = new SchemaService();
|
$schemaService = new SchemaService();
|
||||||
$schemasCollection = (new Collection($schemasArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
|
$schemasCollection = new Collection($schemasArray["schemas"] ?? []);
|
||||||
|
$schemasCollection = $schemasCollection->map(
|
||||||
|
$schemaService->fromArray(...),
|
||||||
|
);
|
||||||
|
|
||||||
$userCommands = [];
|
$userCommands = [];
|
||||||
foreach (config("lucent.commands") ?? [] as $signature => $desc) {
|
foreach (config("lucent.commands") ?? [] as $signature => $desc) {
|
||||||
@@ -42,7 +39,7 @@ final class ChannelService
|
|||||||
commands: Collection::make($userCommands),
|
commands: Collection::make($userCommands),
|
||||||
schemas: $schemasCollection,
|
schemas: $schemasCollection,
|
||||||
imageFilters: config("lucent.imageFilters") ?? [],
|
imageFilters: config("lucent.imageFilters") ?? [],
|
||||||
roles: $schemasArray["roles"] ?? []
|
roles: $schemasArray["roles"] ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
$channelService = new ChannelService($schemaService);
|
$channelService = new ChannelService($schemaService);
|
||||||
@@ -69,11 +66,32 @@ final class ChannelService
|
|||||||
*/
|
*/
|
||||||
public function schemasReadableByRoles(array $roles): array
|
public function schemasReadableByRoles(array $roles): array
|
||||||
{
|
{
|
||||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->pluck("name");
|
$schemasAllRead = $this->channel->schemas
|
||||||
$schemasCanRead = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->read ?? [], $roles)) > 0)->values()->pluck("name");
|
->filter(fn(Schema $schema) => empty($schema->read))
|
||||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
->values()
|
||||||
return $schemasAllRead->merge($schemasCanRead)->merge($schemasCanWrite)->unique()->values()->toArray();
|
->pluck("name");
|
||||||
|
$schemasCanRead = $this->channel->schemas
|
||||||
|
->filter(
|
||||||
|
fn(Schema $schema) => count(
|
||||||
|
array_intersect($schema->read ?? [], $roles),
|
||||||
|
) > 0,
|
||||||
|
)
|
||||||
|
->values()
|
||||||
|
->pluck("name");
|
||||||
|
$schemasCanWrite = $this->channel->schemas
|
||||||
|
->filter(
|
||||||
|
fn(Schema $schema) => count(
|
||||||
|
array_intersect($schema->write ?? [], $roles),
|
||||||
|
) > 0,
|
||||||
|
)
|
||||||
|
->values()
|
||||||
|
->pluck("name");
|
||||||
|
return $schemasAllRead
|
||||||
|
->merge($schemasCanRead)
|
||||||
|
->merge($schemasCanWrite)
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,9 +100,22 @@ final class ChannelService
|
|||||||
*/
|
*/
|
||||||
public function schemasWritableByRoles(array $roles): array
|
public function schemasWritableByRoles(array $roles): array
|
||||||
{
|
{
|
||||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->write ?? []))->values()->pluck("name");
|
$schemasAllRead = $this->channel->schemas
|
||||||
$schemasCanWrite = $this->channel->schemas->filter(fn(Schema $schema) => count(array_intersect($schema->write ?? [], $roles)) > 0)->values()->pluck("name");
|
->filter(fn(Schema $schema) => empty($schema->write ?? []))
|
||||||
return $schemasAllRead->merge($schemasCanWrite)->unique()->values()->toArray();
|
->values()
|
||||||
|
->pluck("name");
|
||||||
|
$schemasCanWrite = $this->channel->schemas
|
||||||
|
->filter(
|
||||||
|
fn(Schema $schema) => count(
|
||||||
|
array_intersect($schema->write ?? [], $roles),
|
||||||
|
) > 0,
|
||||||
|
)
|
||||||
|
->values()
|
||||||
|
->pluck("name");
|
||||||
|
return $schemasAllRead
|
||||||
|
->merge($schemasCanWrite)
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class CommandRepo
|
|||||||
public function findBySignature($signature): ?CommandLogItem
|
public function findBySignature($signature): ?CommandLogItem
|
||||||
{
|
{
|
||||||
|
|
||||||
$row = Database::make()->table("command_logs")->where("signature", $signature)->first();
|
$row = Database::make()->table("lucent_command_logs")->where("signature", $signature)->first();
|
||||||
if (empty($row)) {
|
if (empty($row)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -22,17 +22,17 @@ class CommandRepo
|
|||||||
{
|
{
|
||||||
$foundCommandLogItem = $this->findBySignature($commandLogItem->signature);
|
$foundCommandLogItem = $this->findBySignature($commandLogItem->signature);
|
||||||
if (empty($foundCommandLogItem)) {
|
if (empty($foundCommandLogItem)) {
|
||||||
Database::make()->table("command_logs")->insert(toArray($commandLogItem));
|
Database::make()->table("lucent_command_logs")->insert(toArray($commandLogItem));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::make()->table("command_logs")->where("signature", $commandLogItem->signature)->update(toArray($commandLogItem));
|
Database::make()->table("lucent_command_logs")->where("signature", $commandLogItem->signature)->update(toArray($commandLogItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function appendToLogs(string $signature, string $line): void
|
public function appendToLogs(string $signature, string $line): void
|
||||||
{
|
{
|
||||||
Database::make()->update(
|
Database::make()->update(
|
||||||
'update command_logs set logs = logs || ? where signature = ?',
|
'update lucent_command_logs set logs = logs || ? where signature = ?',
|
||||||
[$line, $signature]
|
[$line, $signature]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,18 @@ namespace Lucent\Commands;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Lucent\Database\Database;
|
use Lucent\Database\Database;
|
||||||
|
|
||||||
class SetupDatabase extends Command
|
class SetupDatabase extends Command
|
||||||
{
|
{
|
||||||
|
protected $signature = "lucent:setup-db";
|
||||||
|
protected $prefix = "lucent_";
|
||||||
|
|
||||||
protected $signature = 'lucent:setup-db';
|
protected $description = "Run to setup a new database";
|
||||||
|
|
||||||
protected $description = 'Run to setup a new database';
|
|
||||||
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|
||||||
$dbConnection = config("lucent.database");
|
|
||||||
$databasePath = config("database.connections.$dbConnection.database");
|
|
||||||
|
|
||||||
if(file_exists($databasePath)){
|
|
||||||
$this->error("Database already exists.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
touch($databasePath);
|
|
||||||
$this->tableUsers();
|
$this->tableUsers();
|
||||||
$this->tableRecords();
|
$this->tableRecords();
|
||||||
$this->tableRevisions();
|
$this->tableRevisions();
|
||||||
@@ -36,79 +27,115 @@ class SetupDatabase extends Command
|
|||||||
|
|
||||||
private function tableUsers(): void
|
private function tableUsers(): void
|
||||||
{
|
{
|
||||||
Database::make()->getSchemaBuilder()->create('users', function (Blueprint $table) {
|
$schema = Database::make()->getSchemaBuilder();
|
||||||
|
if ($schema->hasTable($this->prefix . "users")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$schema->create($this->prefix . "users", function (Blueprint $table) {
|
||||||
$table->uuid("id")->primary();
|
$table->uuid("id")->primary();
|
||||||
$table->string('name')->nullable();
|
$table->string("name")->nullable();
|
||||||
$table->string('email')->unique();
|
$table->string("email")->unique();
|
||||||
$table->jsonb('roles');
|
$table->jsonb("roles");
|
||||||
$table->string('createdAt');
|
$table->string("createdAt");
|
||||||
$table->string('updatedAt');
|
$table->string("updatedAt");
|
||||||
$table->string('loggedInAt');
|
$table->string("loggedInAt");
|
||||||
$table->string('mailToken')->nullable();
|
$table->string("mailToken")->nullable();
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tableSessions(): void
|
private function tableSessions(): void
|
||||||
{
|
{
|
||||||
Database::make()->getSchemaBuilder()->create('sessions', function (Blueprint $table) {
|
$schema = Database::make()->getSchemaBuilder();
|
||||||
$table->string('id')->primary();
|
if ($schema->hasTable($this->prefix . "sessions")) {
|
||||||
$table->foreignId('user_id')->nullable()->index();
|
return;
|
||||||
$table->string('ip_address', 45)->nullable();
|
}
|
||||||
$table->text('user_agent')->nullable();
|
$schema->create($this->prefix . "sessions", function (
|
||||||
$table->longText('payload');
|
Blueprint $table,
|
||||||
$table->integer('last_activity')->index();
|
) {
|
||||||
|
$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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tableRecords(): void
|
private function tableRecords(): void
|
||||||
{
|
{
|
||||||
Database::make()->getSchemaBuilder()->create('records', function (Blueprint $table) {
|
$schema = Database::make()->getSchemaBuilder();
|
||||||
$table->uuid('id')->primary();
|
if (!$schema->hasTable($this->prefix . "records")) {
|
||||||
$table->string('schema');
|
$schema->create($this->prefix . "records", function (
|
||||||
$table->string('status');
|
Blueprint $table,
|
||||||
$table->jsonb('data');
|
) {
|
||||||
$table->jsonb('_sys');
|
$table->uuid("id")->primary();
|
||||||
$table->jsonb('_file');
|
$table->string("schema");
|
||||||
$table->text('search')->default("");
|
$table->string("status");
|
||||||
|
$table->jsonb("data");
|
||||||
|
$table->jsonb("_sys");
|
||||||
|
$table->jsonb("_file");
|
||||||
|
$table->text("search")->default("");
|
||||||
|
|
||||||
$table->index(['schema', '_sys->updatedAt', 'status']);
|
// $table->index(["schema", "_sys->updatedAt", "status"]);
|
||||||
$table->index('search');
|
|
||||||
});
|
|
||||||
|
|
||||||
Database::make()->getSchemaBuilder()->create('edges', function (Blueprint $table) {
|
$table->index("search");
|
||||||
$table->uuid('source');
|
});
|
||||||
$table->uuid('target');
|
|
||||||
$table->string('sourceSchema');
|
|
||||||
$table->string('targetSchema');
|
|
||||||
$table->string('field');
|
|
||||||
$table->string('rank');
|
|
||||||
|
|
||||||
$table->unique(['source', 'target', "field"]);
|
DB::statement(
|
||||||
});
|
"CREATE INDEX ON " .
|
||||||
|
$this->prefix .
|
||||||
|
'records (schema, ((_sys->>\'updatedAt\')), status)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$schema->hasTable($this->prefix . "edges")) {
|
||||||
|
$schema->create($this->prefix . "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"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tableRevisions(): void
|
private function tableRevisions(): void
|
||||||
{
|
{
|
||||||
Database::make()->getSchemaBuilder()->create('revisions', function (Blueprint $table) {
|
$schema = Database::make()->getSchemaBuilder();
|
||||||
$table->uuid('id')->primary();
|
if ($schema->hasTable($this->prefix . "revisions")) {
|
||||||
$table->uuid('recordId');
|
return;
|
||||||
$table->string('schema');
|
}
|
||||||
$table->jsonb('data');
|
$schema->create($this->prefix . "revisions", function (
|
||||||
$table->jsonb('_sys');
|
Blueprint $table,
|
||||||
$table->jsonb('_file');
|
) {
|
||||||
$table->jsonb('_edges');
|
$table->uuid("id")->primary();
|
||||||
|
$table->uuid("recordId");
|
||||||
|
$table->string("schema");
|
||||||
|
$table->jsonb("data");
|
||||||
|
$table->jsonb("_sys");
|
||||||
|
$table->jsonb("_file");
|
||||||
|
$table->jsonb("_edges");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tableCommandLogs(): void
|
private function tableCommandLogs(): void
|
||||||
{
|
{
|
||||||
Database::make()->getSchemaBuilder()->create('command_logs', function (Blueprint $table) {
|
$schema = Database::make()->getSchemaBuilder();
|
||||||
$table->uuid('id')->primary();
|
if ($schema->hasTable($this->prefix . "command_logs")) {
|
||||||
$table->string('signature');
|
return;
|
||||||
$table->integer('pid')->nullable();
|
}
|
||||||
$table->text('logs');
|
$schema->create($this->prefix . "command_logs", function (
|
||||||
|
Blueprint $table,
|
||||||
|
) {
|
||||||
|
$table->uuid("id")->primary();
|
||||||
|
$table->string("signature");
|
||||||
|
$table->integer("pid")->nullable();
|
||||||
|
$table->text("logs");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ class UpgradeFiles122 extends Command
|
|||||||
$schema = $this->argument('schema');
|
$schema = $this->argument('schema');
|
||||||
$disk = $this->argument('disk');
|
$disk = $this->argument('disk');
|
||||||
$db = Database::make();
|
$db = Database::make();
|
||||||
$records = $db->table("records")->where("schema", $schema)->get();
|
$records = $db->table("lucent_records")->where("schema", $schema)->get();
|
||||||
foreach ($records as $record) {
|
foreach ($records as $record) {
|
||||||
$array = json_decode($record->_file, true);
|
$array = json_decode($record->_file, true);
|
||||||
$array["disk"] = $disk;
|
$array["disk"] = $disk;
|
||||||
$db->table("records")->where("id", $record->id)->update(["_file" => json_encode($array)]);
|
$db->table("lucent_records")->where("id", $record->id)->update(["_file" => json_encode($array)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
public static function make(): Connection{
|
public static function make(): Connection
|
||||||
$dbConnection = config("lucent.database");
|
{
|
||||||
|
// $dbConnection = config("lucent.database");
|
||||||
|
|
||||||
return DB::connection($dbConnection);
|
return DB::connection();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class EdgeRepo
|
|||||||
public function insert(Edge $edge): void
|
public function insert(Edge $edge): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Database::make()->table("edges")->insert($edge->toDB());
|
Database::make()->table("lucent_edges")->insert($edge->toDB());
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
if ($e->getCode() == 23505) {
|
if ($e->getCode() == 23505) {
|
||||||
throw new LucentException("Edge already exists");
|
throw new LucentException("Edge already exists");
|
||||||
@@ -34,7 +34,7 @@ class EdgeRepo
|
|||||||
{
|
{
|
||||||
$edgesDB = collect($edges)->map(fn($e) => $e->toDB())->toArray();
|
$edgesDB = collect($edges)->map(fn($e) => $e->toDB())->toArray();
|
||||||
try {
|
try {
|
||||||
Database::make()->table("edges")->insert($edgesDB);
|
Database::make()->table("lucent_edges")->insert($edgesDB);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
if ($e->getCode() == 23505) {
|
if ($e->getCode() == 23505) {
|
||||||
throw new LucentException("Edge already exists");
|
throw new LucentException("Edge already exists");
|
||||||
@@ -52,8 +52,8 @@ class EdgeRepo
|
|||||||
public function replaceForRecord(string $from, array $edges): void
|
public function replaceForRecord(string $from, array $edges): void
|
||||||
{
|
{
|
||||||
$edgesDB = collect($edges)->map(fn($e) => $e->toDB())->toArray();
|
$edgesDB = collect($edges)->map(fn($e) => $e->toDB())->toArray();
|
||||||
Database::make()->table("edges")->where("source", $from)->delete();
|
Database::make()->table("lucent_edges")->where("source", $from)->delete();
|
||||||
Database::make()->table("edges")->insert($edgesDB);
|
Database::make()->table("lucent_edges")->insert($edgesDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -62,13 +62,13 @@ class EdgeRepo
|
|||||||
*/
|
*/
|
||||||
public function findAll(): array
|
public function findAll(): array
|
||||||
{
|
{
|
||||||
$edges = Database::make()->table("edges")->get();
|
$edges = Database::make()->table("lucent_edges")->get();
|
||||||
return $edges->map([$this, 'mapEdge'])->toArray();
|
return $edges->map([$this, 'mapEdge'])->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findForSource(string $recordId): array
|
public function findForSource(string $recordId): array
|
||||||
{
|
{
|
||||||
$edges = Database::make()->table("edges")->where("source", $recordId)->get();
|
$edges = Database::make()->table("lucent_edges")->where("source", $recordId)->get();
|
||||||
return $edges->map([$this, 'mapEdge'])->toArray();
|
return $edges->map([$this, 'mapEdge'])->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class EdgeRepo
|
|||||||
|
|
||||||
public function remove(Edge $edge): void
|
public function remove(Edge $edge): void
|
||||||
{
|
{
|
||||||
Database::make()->table("edges")
|
Database::make()->table("lucent_edges")
|
||||||
->where("source", $edge->source)
|
->where("source", $edge->source)
|
||||||
->where("target", $edge->target)
|
->where("target", $edge->target)
|
||||||
->where("sourceSchema", $edge->sourceSchema)
|
->where("sourceSchema", $edge->sourceSchema)
|
||||||
@@ -100,7 +100,7 @@ class EdgeRepo
|
|||||||
|
|
||||||
public function findLastEdgeRank(string $source, string $field): string
|
public function findLastEdgeRank(string $source, string $field): string
|
||||||
{
|
{
|
||||||
$data = Database::make()->table("edges")
|
$data = Database::make()->table("lucent_edges")
|
||||||
->where("source", $source)
|
->where("source", $source)
|
||||||
->where("field", $field)
|
->where("field", $field)
|
||||||
->orderBy("rank", "desc")
|
->orderBy("rank", "desc")
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class FileService
|
|||||||
private function checkDuplicate(string $schemaName, string $checksum, int $filesize): string
|
private function checkDuplicate(string $schemaName, string $checksum, int $filesize): string
|
||||||
{
|
{
|
||||||
|
|
||||||
$record = Database::make()->table("records")
|
$record = Database::make()->table("lucent_records")
|
||||||
->where("schema", $schemaName)
|
->where("schema", $schemaName)
|
||||||
->where("_file->checksum", $checksum)
|
->where("_file->checksum", $checksum)
|
||||||
->where("_file->size", $filesize)
|
->where("_file->size", $filesize)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace Lucent\Http\Controller;
|
namespace Lucent\Http\Controller;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\Session\Session;
|
use Illuminate\Contracts\Session\Session;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@@ -13,44 +12,37 @@ use Lucent\Account\AuthService;
|
|||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\LucentException;
|
use Lucent\LucentException;
|
||||||
use Lucent\Svelte\Svelte;
|
use Lucent\Svelte\Svelte;
|
||||||
use Lucent\Util\Form\FormException;
|
|
||||||
use Lucent\Util\Form\ResponseFormError;
|
use Lucent\Util\Form\ResponseFormError;
|
||||||
use function Lucent\Response\fail;
|
use function Lucent\Response\fail;
|
||||||
use function Lucent\Response\ok;
|
use function Lucent\Response\ok;
|
||||||
|
|
||||||
|
|
||||||
class AuthController
|
class AuthController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AuthService $authService,
|
private readonly AuthService $authService,
|
||||||
private readonly AccountService $accountService,
|
private readonly AccountService $accountService,
|
||||||
private readonly ChannelService $channelService,
|
private readonly ChannelService $channelService,
|
||||||
private readonly Session $session,
|
private readonly Session $session,
|
||||||
private readonly Svelte $svelte,
|
private readonly Svelte $svelte,
|
||||||
)
|
) {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function register(Request $request): View|RedirectResponse
|
public function register(Request $request): View|RedirectResponse
|
||||||
{
|
{
|
||||||
if ($this->accountService->countUsers() > 0) {
|
if ($this->accountService->countUsers() > 0) {
|
||||||
return redirect($this->channelService->channel->lucentUrl . "/login");
|
return redirect(
|
||||||
|
$this->channelService->channel->lucentUrl . "/login",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "account",
|
layout: "account",
|
||||||
view: "register",
|
view: "register",
|
||||||
title: "Create an account",
|
title: "Create an account",
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function postRegister(Request $request): Response
|
public function postRegister(Request $request): Response
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($this->accountService->countUsers() > 0) {
|
if ($this->accountService->countUsers() > 0) {
|
||||||
abort(400);
|
abort(400);
|
||||||
}
|
}
|
||||||
@@ -70,7 +62,9 @@ class AuthController
|
|||||||
public function login()
|
public function login()
|
||||||
{
|
{
|
||||||
if ($this->accountService->countUsers() == 0) {
|
if ($this->accountService->countUsers() == 0) {
|
||||||
return redirect($this->channelService->channel->lucentUrl . "/register");
|
return redirect(
|
||||||
|
$this->channelService->channel->lucentUrl . "/register",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view("lucent::auth.login");
|
return view("lucent::auth.login");
|
||||||
@@ -89,25 +83,24 @@ class AuthController
|
|||||||
"email" => $request->input("email"),
|
"email" => $request->input("email"),
|
||||||
"token" => $request->input("token"),
|
"token" => $request->input("token"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postVerify(Request $request)
|
public function postVerify(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->authService->login($request->input("email"), $request->input("token"));
|
$this->authService->login(
|
||||||
|
$request->input("email"),
|
||||||
|
$request->input("token"),
|
||||||
|
);
|
||||||
} catch (LucentException $th) {
|
} catch (LucentException $th) {
|
||||||
return ResponseFormError::fromException($th);
|
return ResponseFormError::fromException($th);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function logout(): RedirectResponse
|
public function logout(): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->session->flush();
|
$this->session->flush();
|
||||||
return redirect($this->channelService->channel->lucentUrl . "/login");
|
return redirect($this->channelService->channel->lucentUrl . "/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,13 @@ use function Lucent\Response\ok;
|
|||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Svelte $svelte,
|
private readonly Svelte $svelte,
|
||||||
private readonly AccountService $accountService,
|
private readonly AccountService $accountService,
|
||||||
private readonly Query $query,
|
private readonly Query $query,
|
||||||
)
|
) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function home(): View
|
public function home(): View
|
||||||
{
|
{
|
||||||
|
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "homeIndex",
|
view: "homeIndex",
|
||||||
@@ -38,10 +35,13 @@ class HomeController extends Controller
|
|||||||
|
|
||||||
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
|
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
|
||||||
$filter = data_get($urlParams, "filter") ?? [];
|
$filter = data_get($urlParams, "filter") ?? [];
|
||||||
$arguments = array_merge([
|
$arguments = array_merge(
|
||||||
"schema_in" => $this->accountService->currentReadableSchemas(),
|
[
|
||||||
"status_in" => ["draft", "published"]
|
"schema_in" => $this->accountService->currentReadableSchemas(),
|
||||||
], $filter);
|
"status_in" => ["draft", "published"],
|
||||||
|
],
|
||||||
|
$filter,
|
||||||
|
);
|
||||||
|
|
||||||
$limit = 20;
|
$limit = 20;
|
||||||
|
|
||||||
|
|||||||
@@ -27,23 +27,26 @@ use function Lucent\Response\ok;
|
|||||||
class RecordController extends Controller
|
class RecordController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly RecordService $recordService,
|
private readonly RecordService $recordService,
|
||||||
private readonly AccountService $accountService,
|
private readonly AccountService $accountService,
|
||||||
private readonly ChannelService $channelService,
|
private readonly ChannelService $channelService,
|
||||||
private readonly Svelte $svelte,
|
private readonly Svelte $svelte,
|
||||||
private readonly Query $query,
|
private readonly Query $query,
|
||||||
private readonly Manager $recordManager,
|
private readonly Manager $recordManager,
|
||||||
private readonly OperatorRegistry $operatorRegistry,
|
private readonly OperatorRegistry $operatorRegistry,
|
||||||
private readonly ViewModel $viewModel,
|
private readonly ViewModel $viewModel,
|
||||||
)
|
) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$schemaName = $request->route("schemaName");
|
$schemaName = $request->route("schemaName");
|
||||||
|
|
||||||
if (!in_array($schemaName, $this->accountService->currentReadableSchemas())) {
|
if (
|
||||||
|
!in_array(
|
||||||
|
$schemaName,
|
||||||
|
$this->accountService->currentReadableSchemas(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "recordNotFound",
|
view: "recordNotFound",
|
||||||
@@ -53,16 +56,18 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
$users = $this->accountService->all();
|
$users = $this->accountService->all();
|
||||||
$schema = $this->channelService->getSchema($schemaName)->get();
|
$schema = $this->channelService->getSchema($schemaName)->get();
|
||||||
|
|
||||||
$urlParams = $request->all();
|
$urlParams = $request->all();
|
||||||
$sort = data_get($urlParams, "sort") ?? $schema->sortBy;
|
$sort = data_get($urlParams, "sort") ?? $schema->sortBy;
|
||||||
$filter = data_get($urlParams, "filter") ?? [];
|
$filter = data_get($urlParams, "filter") ?? [];
|
||||||
|
|
||||||
$arguments = array_merge([
|
$arguments = array_merge(
|
||||||
"schema" => $schema->name,
|
[
|
||||||
"status_in" => "draft,published",
|
"schema" => $schema->name,
|
||||||
], $filter);
|
"status_in" => "draft,published",
|
||||||
|
],
|
||||||
|
$filter,
|
||||||
|
);
|
||||||
|
|
||||||
$skip = data_get($urlParams, "skip") ?? 0;
|
$skip = data_get($urlParams, "skip") ?? 0;
|
||||||
$limit = 30;
|
$limit = 30;
|
||||||
@@ -81,7 +86,6 @@ class RecordController extends Controller
|
|||||||
->parentsDepth(0)
|
->parentsDepth(0)
|
||||||
->runWithCount();
|
->runWithCount();
|
||||||
|
|
||||||
|
|
||||||
$records = $graph->getRootRecords()->toArray();
|
$records = $graph->getRootRecords()->toArray();
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
@@ -93,45 +97,64 @@ class RecordController extends Controller
|
|||||||
"systemFields" => array_values(System::list()),
|
"systemFields" => array_values(System::list()),
|
||||||
"operators" => $this->operatorRegistry->all(),
|
"operators" => $this->operatorRegistry->all(),
|
||||||
"sortParam" => $sort,
|
"sortParam" => $sort,
|
||||||
"sortField" => $schema->fields->merge(array_values(System::list()))->firstWhere(fn($field) => $field->name === $sort || "-" . $field->name === $sort || "data." . $field->name === $sort || "-data." . $field->name === $sort),
|
"sortField" => $schema->fields
|
||||||
|
->merge(array_values(System::list()))
|
||||||
|
->firstWhere(
|
||||||
|
fn($field) => $field->name === $sort ||
|
||||||
|
"-" . $field->name === $sort ||
|
||||||
|
"data." . $field->name === $sort ||
|
||||||
|
"-data." . $field->name === $sort,
|
||||||
|
),
|
||||||
"limit" => $limit,
|
"limit" => $limit,
|
||||||
"skip" => $skip,
|
"skip" => $skip,
|
||||||
"total" => $graph->total ?? 0,
|
"total" => $graph->total ?? 0,
|
||||||
"filter" => $request->input("filter") ?? [],
|
"filter" => $request->input("filter") ?? [],
|
||||||
"inModal" => true,
|
"inModal" => true,
|
||||||
"isWritable" => in_array($schemaName, $this->accountService->currentWritableSchemas())
|
"isWritable" => in_array(
|
||||||
|
$schemaName,
|
||||||
|
$this->accountService->currentWritableSchemas(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($request->ajax()) {
|
if ($request->ajax()) {
|
||||||
$data["modalUrl"] = $request->fullUrl();
|
$data["modalUrl"] = $request->fullUrl();
|
||||||
if (str_starts_with(config("lucent.url"), "https")) {
|
if (str_starts_with(config("lucent.url"), "https")) {
|
||||||
$data["modalUrl"] = str_replace("http://", "https://", $request->fullUrl());
|
$data["modalUrl"] = str_replace(
|
||||||
|
"http://",
|
||||||
|
"https://",
|
||||||
|
$request->fullUrl(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
$data["inModal"] = false;
|
$data["inModal"] = false;
|
||||||
|
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "contentIndex",
|
view: "contentIndex",
|
||||||
title: "Records",
|
title: "Records",
|
||||||
data: $data
|
data: $data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportCSV(Request $request)
|
public function exportCSV(Request $request)
|
||||||
{
|
{
|
||||||
$schemaName = $request->route("schemaName");
|
$schemaName = $request->route("schemaName");
|
||||||
$schema = $this->channelService->channel->schemas->where("name", $schemaName)->first();
|
$schema = $this->channelService->channel->schemas
|
||||||
|
->where("name", $schemaName)
|
||||||
|
->first();
|
||||||
|
|
||||||
$urlParams = $request->all();
|
$urlParams = $request->all();
|
||||||
|
|
||||||
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
|
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
|
||||||
$filter = data_get($urlParams, "filter") ?? [];
|
$filter = data_get($urlParams, "filter") ?? [];
|
||||||
$arguments = array_merge([
|
$arguments = array_merge(
|
||||||
"schema" => $schema->name,
|
[
|
||||||
"status_in" => "draft,published",
|
"schema" => $schema->name,
|
||||||
], $filter);
|
"status_in" => "draft,published",
|
||||||
|
],
|
||||||
|
$filter,
|
||||||
|
);
|
||||||
|
|
||||||
$records = $this->query
|
$records = $this->query
|
||||||
->filter($arguments)
|
->filter($arguments)
|
||||||
@@ -143,53 +166,75 @@ class RecordController extends Controller
|
|||||||
->run()
|
->run()
|
||||||
->tree();
|
->tree();
|
||||||
|
|
||||||
header('Content-Type: application/csv');
|
header("Content-Type: application/csv");
|
||||||
header('Content-Disposition: attachment; filename="' . $schemaName . '.csv";');
|
header(
|
||||||
$handle = fopen('php://output', 'w');
|
'Content-Disposition: attachment; filename="' .
|
||||||
|
$schemaName .
|
||||||
|
'.csv";',
|
||||||
|
);
|
||||||
|
$handle = fopen("php://output", "w");
|
||||||
$relationColumns = $this->makeCsvRelationColumns($schema);
|
$relationColumns = $this->makeCsvRelationColumns($schema);
|
||||||
$csvRow = ["id", ...array_keys($records[0]->data->toArray()),...$relationColumns];
|
$csvRow = [
|
||||||
fputcsv($handle, $csvRow, ',');
|
"id",
|
||||||
|
...array_keys($records[0]->data->toArray()),
|
||||||
|
...$relationColumns,
|
||||||
|
];
|
||||||
|
fputcsv($handle, $csvRow, ",");
|
||||||
foreach ($records as $record) {
|
foreach ($records as $record) {
|
||||||
|
|
||||||
$csvRow = [$record->id, ...$record->data->toArray()];
|
$csvRow = [$record->id, ...$record->data->toArray()];
|
||||||
$csvRow = array_merge($csvRow,$this->makeCsvRelationColumnValues($schema,$record->_children));
|
$csvRow = array_merge(
|
||||||
|
$csvRow,
|
||||||
|
$this->makeCsvRelationColumnValues($schema, $record->_children),
|
||||||
|
);
|
||||||
$csvRow = array_values($csvRow);
|
$csvRow = array_values($csvRow);
|
||||||
fputcsv($handle, $csvRow, ',');
|
fputcsv($handle, $csvRow, ",");
|
||||||
}
|
}
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
echo $handle;
|
echo $handle;
|
||||||
exit;
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function makeCsvRelationColumns($schema):array{
|
private function makeCsvRelationColumns($schema): array
|
||||||
return $schema->fields->filter(fn($f) => get_class($f) === Reference::class)->reduce(function($c,$f){
|
{
|
||||||
$c[] = $f->name." id";
|
return $schema->fields
|
||||||
$c[] = $f->name." name";
|
->filter(fn($f) => get_class($f) === Reference::class)
|
||||||
return $c;
|
->reduce(function ($c, $f) {
|
||||||
},[]);
|
$c[] = $f->name . " id";
|
||||||
|
$c[] = $f->name . " name";
|
||||||
|
return $c;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function makeCsvRelationColumnValues($schema, $children):array{
|
private function makeCsvRelationColumnValues($schema, $children): array
|
||||||
return $schema->fields->filter(fn($f) => get_class($f) === Reference::class)->reduce(function($c,$f) use($children){
|
{
|
||||||
|
return $schema->fields
|
||||||
$fieldRecords = data_get($children,$f->name);
|
->filter(fn($f) => get_class($f) === Reference::class)
|
||||||
if(empty($fieldRecords)){
|
->reduce(function ($c, $f) use ($children) {
|
||||||
$c[] = "";
|
$fieldRecords = data_get($children, $f->name);
|
||||||
$c[] = "";
|
if (empty($fieldRecords)) {
|
||||||
}elseif (count($fieldRecords) === 1){
|
$c[] = "";
|
||||||
$c[] = data_get($fieldRecords,"0.id");
|
$c[] = "";
|
||||||
$c[] = $this->viewModel->getRecordName($fieldRecords[0]);
|
} elseif (count($fieldRecords) === 1) {
|
||||||
}else{
|
$c[] = data_get($fieldRecords, "0.id");
|
||||||
$c[] = collect($fieldRecords)->pluck("id")->join("::");
|
$c[] = $this->viewModel->getRecordName($fieldRecords[0]);
|
||||||
$c[] = collect($fieldRecords)->pluck("data.name")->join("::");
|
} else {
|
||||||
}
|
$c[] = collect($fieldRecords)->pluck("id")->join("::");
|
||||||
return $c;
|
$c[] = collect($fieldRecords)
|
||||||
},[]);
|
->pluck("data.name")
|
||||||
|
->join("::");
|
||||||
|
}
|
||||||
|
return $c;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function new(Request $request)
|
public function new(Request $request)
|
||||||
{
|
{
|
||||||
if (!in_array($request->input("schema"), $this->accountService->currentWritableSchemas())) {
|
if (
|
||||||
|
!in_array(
|
||||||
|
$request->input("schema"),
|
||||||
|
$this->accountService->currentWritableSchemas(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "recordNotFound",
|
view: "recordNotFound",
|
||||||
@@ -197,8 +242,12 @@ class RecordController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$schema = $this->channelService->channel->schemas->where("name", $request->input("schema"))->first();
|
$schema = $this->channelService->channel->schemas
|
||||||
$recordHistory = $this->recordManager->fromSession($request->session())->getRecords();
|
->where("name", $request->input("schema"))
|
||||||
|
->first();
|
||||||
|
$recordHistory = $this->recordManager
|
||||||
|
->fromSession($request->session())
|
||||||
|
->getRecords();
|
||||||
$record = $this->recordService->createEmpty($schema);
|
$record = $this->recordService->createEmpty($schema);
|
||||||
$queryRecord = QueryRecord::fromRecord($record);
|
$queryRecord = QueryRecord::fromRecord($record);
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
@@ -210,15 +259,22 @@ class RecordController extends Controller
|
|||||||
"record" => $queryRecord,
|
"record" => $queryRecord,
|
||||||
"recordHistory" => $recordHistory,
|
"recordHistory" => $recordHistory,
|
||||||
"isCreateMode" => true,
|
"isCreateMode" => true,
|
||||||
"isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas())
|
"isWritable" => in_array(
|
||||||
]
|
$record->schema,
|
||||||
|
$this->accountService->currentWritableSchemas(),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function newInline(Request $request)
|
public function newInline(Request $request)
|
||||||
{
|
{
|
||||||
if (!in_array($request->input("schema"), $this->accountService->currentWritableSchemas())) {
|
if (
|
||||||
|
!in_array(
|
||||||
|
$request->input("schema"),
|
||||||
|
$this->accountService->currentWritableSchemas(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "recordNotFound",
|
view: "recordNotFound",
|
||||||
@@ -226,7 +282,9 @@ class RecordController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$schema = $this->channelService->getSchema($request->input("schema"))->get();
|
$schema = $this->channelService
|
||||||
|
->getSchema($request->input("schema"))
|
||||||
|
->get();
|
||||||
$record = $this->recordService->createEmpty($schema);
|
$record = $this->recordService->createEmpty($schema);
|
||||||
$queryRecord = QueryRecord::fromRecord($record);
|
$queryRecord = QueryRecord::fromRecord($record);
|
||||||
|
|
||||||
@@ -234,7 +292,10 @@ class RecordController extends Controller
|
|||||||
"schema" => $schema,
|
"schema" => $schema,
|
||||||
"record" => $queryRecord,
|
"record" => $queryRecord,
|
||||||
"isCreateMode" => true,
|
"isCreateMode" => true,
|
||||||
"isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas())
|
"isWritable" => in_array(
|
||||||
|
$record->schema,
|
||||||
|
$this->accountService->currentWritableSchemas(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +323,12 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
$record = $graph->records->first();
|
$record = $graph->records->first();
|
||||||
|
|
||||||
if (!in_array($record->schema, $this->accountService->currentReadableSchemas())) {
|
if (
|
||||||
|
!in_array(
|
||||||
|
$record->schema,
|
||||||
|
$this->accountService->currentReadableSchemas(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "recordNotFound",
|
view: "recordNotFound",
|
||||||
@@ -271,7 +337,10 @@ class RecordController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$schema = $this->channelService->getSchema($record->schema)->get();
|
$schema = $this->channelService->getSchema($record->schema)->get();
|
||||||
$recordHistory = $this->recordManager->fromSession($request->session())->push($rid)->getRecords($rid);
|
$recordHistory = $this->recordManager
|
||||||
|
->fromSession($request->session())
|
||||||
|
->push($rid)
|
||||||
|
->getRecords($rid);
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "recordEdit",
|
view: "recordEdit",
|
||||||
@@ -282,8 +351,11 @@ class RecordController extends Controller
|
|||||||
"record" => toArray($record),
|
"record" => toArray($record),
|
||||||
"users" => $this->accountService->all(),
|
"users" => $this->accountService->all(),
|
||||||
"recordHistory" => $recordHistory,
|
"recordHistory" => $recordHistory,
|
||||||
"isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas())
|
"isWritable" => in_array(
|
||||||
]
|
$record->schema,
|
||||||
|
$this->accountService->currentWritableSchemas(),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,19 +367,20 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
if ($request->input("value")) {
|
if ($request->input("value")) {
|
||||||
if (in_array($request->input("ui"), ["text", "date"])) {
|
if (in_array($request->input("ui"), ["text", "date"])) {
|
||||||
$arguments["data." . $request->input("field") . "_regex"] = $request->input("value");
|
$arguments[
|
||||||
|
"data." . $request->input("field") . "_regex"
|
||||||
|
] = $request->input("value");
|
||||||
} elseif ($request->input("ui") == "number") {
|
} elseif ($request->input("ui") == "number") {
|
||||||
$arguments["data." . $request->input("field") . "_eqnum"] = floatval($request->input("value"));
|
$arguments[
|
||||||
|
"data." . $request->input("field") . "_eqnum"
|
||||||
|
] = floatval($request->input("value"));
|
||||||
} elseif ($request->input("ui") == "date") {
|
} elseif ($request->input("ui") == "date") {
|
||||||
} elseif ($request->input("ui") == "search") {
|
} elseif ($request->input("ui") == "search") {
|
||||||
$arguments["search_regex"] = $request->input("value");
|
$arguments["search_regex"] = $request->input("value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$records = $this->query
|
$records = $this->query->filter($arguments)->limit(10)->tree();
|
||||||
->filter($arguments)
|
|
||||||
->limit(10)
|
|
||||||
->tree();
|
|
||||||
|
|
||||||
if ($records->isEmpty()) {
|
if ($records->isEmpty()) {
|
||||||
return ok([]);
|
return ok([]);
|
||||||
@@ -320,7 +393,6 @@ class RecordController extends Controller
|
|||||||
{
|
{
|
||||||
$recordId = $request->input("record.id");
|
$recordId = $request->input("record.id");
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if ($request->input("isCreateMode")) {
|
if ($request->input("isCreateMode")) {
|
||||||
$recordId = $this->recordService->create(
|
$recordId = $this->recordService->create(
|
||||||
data: new RecordInputData(
|
data: new RecordInputData(
|
||||||
@@ -329,15 +401,20 @@ class RecordController extends Controller
|
|||||||
$request->input("record.data"),
|
$request->input("record.data"),
|
||||||
Status::from($request->input("record.status")),
|
Status::from($request->input("record.status")),
|
||||||
),
|
),
|
||||||
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? [])
|
edges: array_map(
|
||||||
|
EdgeInputData::fromArray(...),
|
||||||
|
$request->input("edges") ?? [],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$this->recordService->updateWithEdges(
|
$this->recordService->updateWithEdges(
|
||||||
id: $request->input("record.id"),
|
id: $request->input("record.id"),
|
||||||
data: $request->input("record.data"),
|
data: $request->input("record.data"),
|
||||||
status: Status::from($request->input("record.status")),
|
status: Status::from($request->input("record.status")),
|
||||||
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
|
edges: array_map(
|
||||||
|
EdgeInputData::fromArray(...),
|
||||||
|
$request->input("edges") ?? [],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +424,6 @@ class RecordController extends Controller
|
|||||||
->childrenDepth(1)
|
->childrenDepth(1)
|
||||||
->parentsDepth(1)
|
->parentsDepth(1)
|
||||||
->run();
|
->run();
|
||||||
|
|
||||||
} catch (ValidatorException $th) {
|
} catch (ValidatorException $th) {
|
||||||
return fail($th->getValidatorErrors());
|
return fail($th->getValidatorErrors());
|
||||||
} catch (LucentException $th) {
|
} catch (LucentException $th) {
|
||||||
@@ -356,11 +432,9 @@ class RecordController extends Controller
|
|||||||
return ok(toArray($newGraph));
|
return ok(toArray($newGraph));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function clone(Request $request)
|
public function clone(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$newRecordId = $this->recordService->clone(
|
$newRecordId = $this->recordService->clone(
|
||||||
recordId: $request->route("rid"),
|
recordId: $request->route("rid"),
|
||||||
);
|
);
|
||||||
@@ -374,10 +448,8 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
public function status(Request $request)
|
public function status(Request $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
$ids = array_map(fn($rec) => $rec["id"], $request->input("records"));
|
$ids = array_map(fn($rec) => $rec["id"], $request->input("records"));
|
||||||
|
|
||||||
|
|
||||||
$this->recordService->changeStatusBulk(
|
$this->recordService->changeStatusBulk(
|
||||||
status: $request->route("status"),
|
status: $request->route("status"),
|
||||||
recordsIds: $ids,
|
recordsIds: $ids,
|
||||||
@@ -387,9 +459,12 @@ class RecordController extends Controller
|
|||||||
|
|
||||||
public function emptyTrash(Request $request)
|
public function emptyTrash(Request $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->recordService->emptyTrash($request->route("schemaName"));
|
$this->recordService->emptyTrash($request->route("schemaName"));
|
||||||
return redirect($this->channelService->channel->lucentUrl . "/content/" . $request->route("schemaName"));
|
return redirect(
|
||||||
|
$this->channelService->channel->lucentUrl .
|
||||||
|
"/content/" .
|
||||||
|
$request->route("schemaName"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Request $request)
|
public function delete(Request $request)
|
||||||
@@ -409,11 +484,11 @@ class RecordController extends Controller
|
|||||||
try {
|
try {
|
||||||
$this->recordService->rollback(
|
$this->recordService->rollback(
|
||||||
recordId: $request->route("rid"),
|
recordId: $request->route("rid"),
|
||||||
version: (int)$request->route("version")
|
version: (int) $request->route("version"),
|
||||||
);
|
);
|
||||||
} catch (ValidatorException $th) {
|
} catch (ValidatorException $th) {
|
||||||
return fail($th->getFirstValidatorError());
|
return fail($th->getFirstValidatorError());
|
||||||
} catch (LucentException|Throwable $th) {
|
} catch (LucentException | Throwable $th) {
|
||||||
return fail($th);
|
return fail($th);
|
||||||
}
|
}
|
||||||
return ok();
|
return ok();
|
||||||
|
|||||||
@@ -16,21 +16,29 @@ readonly class AuthMiddleware
|
|||||||
private AuthService $authService,
|
private AuthService $authService,
|
||||||
private AccountService $accountService,
|
private AccountService $accountService,
|
||||||
private ChannelService $channelService,
|
private ChannelService $channelService,
|
||||||
private ViewModel $viewModel
|
private ViewModel $viewModel,
|
||||||
)
|
) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
if (!$this->authService->isLoggedIn()) {
|
if (!$this->authService->isLoggedIn()) {
|
||||||
return redirect($this->channelService->channel->lucentUrl . "/login");
|
return redirect(
|
||||||
|
$this->channelService->channel->lucentUrl . "/login",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$this->authService->refreshSession();
|
$this->authService->refreshSession();
|
||||||
View::share("channel",$this->channelService->channel);
|
View::share("channel", $this->channelService->channel);
|
||||||
View::share("user",session("user"));
|
View::share("user", toArray($this->authService->getCurrentUser()));
|
||||||
View::share("schemas",$this->channelService->channel->schemas->whereIn("name",$this->accountService->currentReadableSchemas())->values());
|
View::share(
|
||||||
View::share("viewModel",$this->viewModel);
|
"schemas",
|
||||||
|
$this->channelService->channel->schemas
|
||||||
|
->whereIn(
|
||||||
|
"name",
|
||||||
|
$this->accountService->currentReadableSchemas(),
|
||||||
|
)
|
||||||
|
->values(),
|
||||||
|
);
|
||||||
|
View::share("viewModel", $this->viewModel);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ use Illuminate\Support\Facades\Blade;
|
|||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
|
use Lucent\Account\AuthService;
|
||||||
|
use Lucent\Account\AuthServiceLunar;
|
||||||
|
use Lucent\Account\UserRepo;
|
||||||
|
use Lucent\Account\UserRepoLunar;
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
use Lucent\Commands\CompileSchemas;
|
use Lucent\Commands\CompileSchemas;
|
||||||
use Lucent\Commands\GenerateCollectionSchema;
|
use Lucent\Commands\GenerateCollectionSchema;
|
||||||
@@ -17,10 +21,8 @@ use Lucent\Commands\RemoveOrphanEdges;
|
|||||||
use Lucent\Commands\SetupDatabase;
|
use Lucent\Commands\SetupDatabase;
|
||||||
use Lucent\Commands\UpgradeFiles122;
|
use Lucent\Commands\UpgradeFiles122;
|
||||||
use Lucent\File\FileService;
|
use Lucent\File\FileService;
|
||||||
use Lucent\File\ImageService;
|
|
||||||
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
||||||
use Lucent\Query\DatabaseGraph\PgsqlDatabaseGraph;
|
use Lucent\Query\DatabaseGraph\PgsqlDatabaseGraph;
|
||||||
use Lucent\Query\DatabaseGraph\SqliteDatabaseGraph;
|
|
||||||
|
|
||||||
class LucentServiceProvider extends ServiceProvider
|
class LucentServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -34,21 +36,20 @@ class LucentServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
$this->app->bind(ImageManager::class, function () {
|
$this->app->bind(ImageManager::class, function () {
|
||||||
return new ImageManager(['driver' => 'imagick']);
|
return new ImageManager(["driver" => "imagick"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$this->app->bind(DatabaseGraph::class, function () {
|
$this->app->bind(DatabaseGraph::class, function () {
|
||||||
$dbConnection = config("lucent.database");
|
return new PgsqlDatabaseGraph();
|
||||||
return match (config("database.connections.$dbConnection.driver")) {
|
|
||||||
"sqlite" => new SqliteDatabaseGraph(),
|
|
||||||
"pgsql" => new PgsqlDatabaseGraph(),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->app->bind(UserRepo::class, function () {
|
||||||
|
return new UserRepoLunar();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->app->bind(AuthService::class, function ($app) {
|
||||||
|
return $app->make(AuthServiceLunar::class);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,20 +57,29 @@ class LucentServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(Router $router): void
|
public function boot(Router $router): void
|
||||||
{
|
{
|
||||||
|
$manifestPath = public_path("vendor/lucent/dist/manifest.json");
|
||||||
$manifestPath = public_path('vendor/lucent/dist/manifest.json');
|
|
||||||
$manifest = null;
|
$manifest = null;
|
||||||
if (file_exists($manifestPath)) {
|
if (file_exists($manifestPath)) {
|
||||||
$manifest = json_decode(file_get_contents(public_path('vendor/lucent/dist/manifest.json')), true);
|
$manifest = json_decode(
|
||||||
|
file_get_contents(
|
||||||
|
public_path("vendor/lucent/dist/manifest.json"),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$router->aliasMiddleware(
|
||||||
|
"lucent.auth",
|
||||||
|
\Lucent\Http\Middleware\AuthMiddleware::class,
|
||||||
|
);
|
||||||
|
$router->aliasMiddleware(
|
||||||
|
"lucent.guest",
|
||||||
|
\Lucent\Http\Middleware\GuestMiddleware::class,
|
||||||
|
);
|
||||||
|
|
||||||
$router->aliasMiddleware('lucent.auth', \Lucent\Http\Middleware\AuthMiddleware::class);
|
$this->loadViewsFrom(__DIR__ . "/../front/views", "lucent");
|
||||||
$router->aliasMiddleware('lucent.guest', \Lucent\Http\Middleware\GuestMiddleware::class);
|
$this->loadRoutesFrom(__DIR__ . "/Http/web.php");
|
||||||
|
$this->loadRoutesFrom(__DIR__ . "/Http/api.php");
|
||||||
$this->loadViewsFrom(__DIR__ . '/../front/views', 'lucent');
|
|
||||||
$this->loadRoutesFrom(__DIR__ . '/Http/web.php');
|
|
||||||
$this->loadRoutesFrom(__DIR__ . '/Http/api.php');
|
|
||||||
|
|
||||||
if ($this->app->runningInConsole()) {
|
if ($this->app->runningInConsole()) {
|
||||||
$this->commands([
|
$this->commands([
|
||||||
@@ -84,19 +94,28 @@ class LucentServiceProvider extends ServiceProvider
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
View::share('manifest', $manifest);
|
View::share("manifest", $manifest);
|
||||||
View::share('file', app()->make(FileService::class));
|
View::share("file", app()->make(FileService::class));
|
||||||
Blade::anonymousComponentPath(__DIR__ . '../front/views/components', "lucent");
|
Blade::anonymousComponentPath(
|
||||||
|
__DIR__ . "../front/views/components",
|
||||||
$this->publishes([
|
"lucent",
|
||||||
__DIR__ . '/Config/main.php' => config_path('lucent.php'),
|
);
|
||||||
],"lucent-config");
|
|
||||||
|
|
||||||
$this->publishes([
|
|
||||||
__DIR__ . '/../front/dist' => public_path('vendor/lucent/dist'),
|
|
||||||
__DIR__ . '/../front/public' => public_path('vendor/lucent/public'),
|
|
||||||
], 'lucent');
|
|
||||||
|
|
||||||
|
$this->publishes(
|
||||||
|
[
|
||||||
|
__DIR__ . "/Config/main.php" => config_path("lucent.php"),
|
||||||
|
],
|
||||||
|
"lucent-config",
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->publishes(
|
||||||
|
[
|
||||||
|
__DIR__ . "/../front/dist" => public_path("vendor/lucent/dist"),
|
||||||
|
__DIR__ . "/../front/public" => public_path(
|
||||||
|
"vendor/lucent/public",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"lucent",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class PgsqlDatabaseGraph implements DatabaseGraph
|
|||||||
*/
|
*/
|
||||||
public function getChildren(array $ids, QueryOptions $options): array
|
public function getChildren(array $ids, QueryOptions $options): array
|
||||||
{
|
{
|
||||||
$subquery = Database::make()->table('edges AS g')
|
$subquery = Database::make()->table('lucent_edges AS g')
|
||||||
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
|
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
|
||||||
->whereIn('source', $ids);
|
->whereIn('source', $ids);
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ class PgsqlDatabaseGraph implements DatabaseGraph
|
|||||||
|
|
||||||
$subquery->limit($options->childrenLimit)
|
$subquery->limit($options->childrenLimit)
|
||||||
->unionAll(
|
->unionAll(
|
||||||
Database::make()->table(DB::raw("edges AS g, search_graph AS sg "))
|
Database::make()->table(DB::raw("lucent_edges AS g, search_graph AS sg "))
|
||||||
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
||||||
->whereRaw("g.source = sg.target")
|
->whereRaw("g.source = sg.target")
|
||||||
->where("depth", "<", $options->childrenDepth)
|
->where("depth", "<", $options->childrenDepth)
|
||||||
@@ -42,12 +42,12 @@ class PgsqlDatabaseGraph implements DatabaseGraph
|
|||||||
*/
|
*/
|
||||||
public function getParents(array $ids, QueryOptions $options): array
|
public function getParents(array $ids, QueryOptions $options): array
|
||||||
{
|
{
|
||||||
$subquery = Database::make()->table('edges AS g')
|
$subquery = Database::make()->table('lucent_edges AS g')
|
||||||
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
|
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
|
||||||
->limit($options->parentsLimit)
|
->limit($options->parentsLimit)
|
||||||
->whereIn('g.target', $ids)
|
->whereIn('g.target', $ids)
|
||||||
->unionAll(
|
->unionAll(
|
||||||
Database::make()->table(DB::raw("edges AS g, search_graph AS sg "))
|
Database::make()->table(DB::raw("lucent_edges AS g, search_graph AS sg "))
|
||||||
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
||||||
->whereRaw("g.target = sg.source")
|
->whereRaw("g.target = sg.source")
|
||||||
->where("depth", "<", $options->parentsDepth)
|
->where("depth", "<", $options->parentsDepth)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class SqliteDatabaseGraph implements DatabaseGraph
|
|||||||
*/
|
*/
|
||||||
public function getChildren(array $ids, QueryOptions $options): array
|
public function getChildren(array $ids, QueryOptions $options): array
|
||||||
{
|
{
|
||||||
$subquery = Database::make()->table('edges AS g')
|
$subquery = Database::make()->table('lucent_edges AS g')
|
||||||
->select(DB::raw('g.source,g.target,g.rank,g.sourceSchema,g.targetSchema,g.field, 1 as depth '))
|
->select(DB::raw('g.source,g.target,g.rank,g.sourceSchema,g.targetSchema,g.field, 1 as depth '))
|
||||||
->whereIn('source', $ids);
|
->whereIn('source', $ids);
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ class SqliteDatabaseGraph implements DatabaseGraph
|
|||||||
|
|
||||||
$subquery->limit($options->childrenLimit)
|
$subquery->limit($options->childrenLimit)
|
||||||
->unionAll(
|
->unionAll(
|
||||||
Database::make()->table(DB::raw("edges AS g, search_graph AS sg "))
|
Database::make()->table(DB::raw("lucent_edges AS g, search_graph AS sg "))
|
||||||
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
||||||
->whereRaw("g.source = sg.target")
|
->whereRaw("g.source = sg.target")
|
||||||
->where("depth", "<", $options->childrenDepth)
|
->where("depth", "<", $options->childrenDepth)
|
||||||
@@ -41,12 +41,12 @@ class SqliteDatabaseGraph implements DatabaseGraph
|
|||||||
*/
|
*/
|
||||||
public function getParents(array $ids, QueryOptions $options): array
|
public function getParents(array $ids, QueryOptions $options): array
|
||||||
{
|
{
|
||||||
$subquery = Database::make()->table('edges AS g')
|
$subquery = Database::make()->table('lucent_edges AS g')
|
||||||
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
|
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
|
||||||
->limit($options->parentsLimit)
|
->limit($options->parentsLimit)
|
||||||
->whereIn('g.target', $ids)
|
->whereIn('g.target', $ids)
|
||||||
->unionAll(
|
->unionAll(
|
||||||
Database::make()->table(DB::raw("edges AS g, search_graph AS sg "))
|
Database::make()->table(DB::raw("lucent_edges AS g, search_graph AS sg "))
|
||||||
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
||||||
->whereRaw("g.target = sg.source")
|
->whereRaw("g.target = sg.source")
|
||||||
->where("depth", "<", $options->parentsDepth)
|
->where("depth", "<", $options->parentsDepth)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ final class FilterParser
|
|||||||
}
|
}
|
||||||
|
|
||||||
$targetIds = collect($graph->records)->pluck("id");
|
$targetIds = collect($graph->records)->pluck("id");
|
||||||
$sourceIds = Database::make()->table("edges")->whereIn("target", $targetIds)->where("field", $k)->get()->pluck("source");
|
$sourceIds = Database::make()->table("lucent_edges")->whereIn("target", $targetIds)->where("field", $k)->get()->pluck("source");
|
||||||
return array_merge($c, $sourceIds->toArray());
|
return array_merge($c, $sourceIds->toArray());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
+90
-68
@@ -13,9 +13,8 @@ use Lucent\Record\InputFormatter;
|
|||||||
use Lucent\Record\QueryRecord;
|
use Lucent\Record\QueryRecord;
|
||||||
use Lucent\Record\Record;
|
use Lucent\Record\Record;
|
||||||
|
|
||||||
final class Query
|
final class Query
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<AndFilter> $filters
|
* @var array<AndFilter> $filters
|
||||||
*/
|
*/
|
||||||
@@ -23,11 +22,10 @@ final class Query
|
|||||||
public QueryOptions $options;
|
public QueryOptions $options;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly FilterParser $filterParser,
|
public readonly FilterParser $filterParser,
|
||||||
public readonly InputFormatter $inputFormatter,
|
public readonly InputFormatter $inputFormatter,
|
||||||
public readonly DatabaseGraph $databaseGraph,
|
public readonly DatabaseGraph $databaseGraph,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
$this->options = new QueryOptions();
|
$this->options = new QueryOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +41,6 @@ final class Query
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function run(): Graph
|
public function run(): Graph
|
||||||
{
|
{
|
||||||
$resultsRecords = $this->mainQuery();
|
$resultsRecords = $this->mainQuery();
|
||||||
@@ -55,33 +52,53 @@ final class Query
|
|||||||
$resultChildrenEdges = [];
|
$resultChildrenEdges = [];
|
||||||
if ($this->options->childrenDepth > 0 && !empty($ids)) {
|
if ($this->options->childrenDepth > 0 && !empty($ids)) {
|
||||||
$resultChildrenEdges = $this->getChildren($ids);
|
$resultChildrenEdges = $this->getChildren($ids);
|
||||||
$resultChildrenEdgesTargetIds = array_map(fn($e) => $e->target, $resultChildrenEdges);
|
$resultChildrenEdgesTargetIds = array_map(
|
||||||
|
fn($e) => $e->target,
|
||||||
|
$resultChildrenEdges,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$resultParentSourceTargetIds = [];
|
$resultParentSourceTargetIds = [];
|
||||||
$resultParentEdges = [];
|
$resultParentEdges = [];
|
||||||
if ($this->options->parentsDepth > 0 && !empty($ids)) {
|
if ($this->options->parentsDepth > 0 && !empty($ids)) {
|
||||||
$resultParentEdges = $this->getParents($ids);
|
$resultParentEdges = $this->getParents($ids);
|
||||||
$resultParentSourceTargetIds = array_map(fn($e) => $e->source, $resultParentEdges);
|
$resultParentSourceTargetIds = array_map(
|
||||||
|
fn($e) => $e->source,
|
||||||
|
$resultParentEdges,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$edgesIds = collect($resultParentSourceTargetIds)->merge($resultChildrenEdgesTargetIds)->unique()->values()->toArray();
|
$edgesIds = collect($resultParentSourceTargetIds)
|
||||||
|
->merge($resultChildrenEdgesTargetIds)
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
$edgeRecords = [];
|
$edgeRecords = [];
|
||||||
if (!empty($edgesIds)) {
|
if (!empty($edgesIds)) {
|
||||||
$edgeRecords = Database::make()->table('records')
|
$edgeRecords = Database::make()
|
||||||
|
->table("lucent_records")
|
||||||
->whereIn("id", $edgesIds)
|
->whereIn("id", $edgesIds)
|
||||||
->whereIn("status", $this->options->status)
|
->whereIn("status", $this->options->status)
|
||||||
->get()->toArray();
|
->get()
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
$resultsRecordsUnique = collect(array_merge($resultsRecords, $edgeRecords))->unique("id")->values()->toArray();
|
$resultsRecordsUnique = collect(
|
||||||
// $resultEdges = collect(array_merge($resultChildrenEdges, $resultParentEdges))
|
array_merge($resultsRecords, $edgeRecords),
|
||||||
// ->unique(fn($edge) => $edge->source . $edge->target . $edge->field)
|
)
|
||||||
// ->toArray();
|
->unique("id")
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
// $resultEdges = collect(array_merge($resultChildrenEdges, $resultParentEdges))
|
||||||
|
// ->unique(fn($edge) => $edge->source . $edge->target . $edge->field)
|
||||||
|
// ->toArray();
|
||||||
|
|
||||||
$formattedRecords = $this->formatRecords($resultsRecordsUnique, $resultChildrenEdges, $resultParentEdges);
|
$formattedRecords = $this->formatRecords(
|
||||||
|
$resultsRecordsUnique,
|
||||||
|
$resultChildrenEdges,
|
||||||
|
$resultParentEdges,
|
||||||
|
);
|
||||||
|
|
||||||
$this->reset();
|
$this->reset();
|
||||||
return $formattedRecords;
|
return $formattedRecords;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function reset()
|
private function reset()
|
||||||
@@ -90,33 +107,41 @@ final class Query
|
|||||||
$this->filters = [];
|
$this->filters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatRecords(array $records, array $edges, array $parentEdges): Graph
|
private function formatRecords(
|
||||||
{
|
array $records,
|
||||||
$queryRecords = collect($records)->map(function ($recordData) {
|
array $edges,
|
||||||
|
array $parentEdges,
|
||||||
|
): Graph {
|
||||||
|
$queryRecords = collect($records)
|
||||||
|
->map(function ($recordData) {
|
||||||
|
$record = Record::fromDB($recordData);
|
||||||
|
|
||||||
$record = Record::fromDB($recordData);
|
$record->data = $this->inputFormatter->fill(
|
||||||
|
$record->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;
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
|
||||||
$queryRecord = QueryRecord::fromRecord($record);
|
$queryEdges = collect($edges)
|
||||||
$queryRecord->isRoot = data_get($recordData, "isRoot") === true;
|
->map(function ($edgeData) {
|
||||||
return $queryRecord;
|
return Edge::fromArray((array) $edgeData);
|
||||||
})->toArray();
|
})
|
||||||
|
->sortBy("rank")
|
||||||
|
->values()
|
||||||
$queryEdges = collect($edges)->map(function ($edgeData) {
|
->toArray();
|
||||||
|
|
||||||
return Edge::fromArray((array)$edgeData);
|
|
||||||
|
|
||||||
})->sortBy("rank")->values()->toArray();
|
|
||||||
|
|
||||||
$queryParentEdges = collect($parentEdges)->map(function ($edgeData) {
|
|
||||||
|
|
||||||
|
|
||||||
return Edge::fromArray((array)$edgeData);
|
|
||||||
|
|
||||||
})->sortBy("rank")->values()->toArray();
|
|
||||||
|
|
||||||
|
$queryParentEdges = collect($parentEdges)
|
||||||
|
->map(function ($edgeData) {
|
||||||
|
return Edge::fromArray((array) $edgeData);
|
||||||
|
})
|
||||||
|
->sortBy("rank")
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
return new Graph(
|
return new Graph(
|
||||||
new Collection($queryRecords),
|
new Collection($queryRecords),
|
||||||
@@ -126,13 +151,11 @@ final class Query
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function tree(): Collection
|
public function tree(): Collection
|
||||||
{
|
{
|
||||||
return $this->run()->tree();
|
return $this->run()->tree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function parseFilters(Builder $query): Builder
|
private function parseFilters(Builder $query): Builder
|
||||||
{
|
{
|
||||||
foreach ($this->filters as $filter) {
|
foreach ($this->filters as $filter) {
|
||||||
@@ -142,10 +165,9 @@ final class Query
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function mainQuery(): array
|
private function mainQuery(): array
|
||||||
{
|
{
|
||||||
$query = Database::make()->table("records");
|
$query = Database::make()->table("lucent_records");
|
||||||
$query = $this->parseFilters($query);
|
$query = $this->parseFilters($query);
|
||||||
$query = $this->findNotLinked($query);
|
$query = $this->findNotLinked($query);
|
||||||
|
|
||||||
@@ -155,13 +177,15 @@ final class Query
|
|||||||
}
|
}
|
||||||
|
|
||||||
$query = $this->orderByQuery($query);
|
$query = $this->orderByQuery($query);
|
||||||
return $query->get()->map(function ($r) {
|
return $query
|
||||||
$r->isRoot = true;
|
->get()
|
||||||
return $r;
|
->map(function ($r) {
|
||||||
})->toArray();
|
$r->isRoot = true;
|
||||||
|
return $r;
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function findNotLinked(Builder $query): Builder
|
private function findNotLinked(Builder $query): Builder
|
||||||
{
|
{
|
||||||
if (empty($this->options->notLinked)) {
|
if (empty($this->options->notLinked)) {
|
||||||
@@ -170,14 +194,19 @@ final class Query
|
|||||||
|
|
||||||
// This returns only records that have no parents
|
// This returns only records that have no parents
|
||||||
$query
|
$query
|
||||||
->select("records.*")
|
->select("lucent_records.*")
|
||||||
->join('edges', 'records.id', '=', 'edges.target', 'left outer')
|
->join(
|
||||||
->whereNull("edges.target");
|
"lucent_edges",
|
||||||
|
"lucent_records.id",
|
||||||
|
"=",
|
||||||
|
"lucent_edges.target",
|
||||||
|
"left outer",
|
||||||
|
)
|
||||||
|
->whereNull("lucent_edges.target");
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private function getChildren(array $ids): array
|
||||||
function getChildren(array $ids): array
|
|
||||||
{
|
{
|
||||||
return $this->databaseGraph->getChildren($ids, $this->options);
|
return $this->databaseGraph->getChildren($ids, $this->options);
|
||||||
}
|
}
|
||||||
@@ -187,12 +216,9 @@ final class Query
|
|||||||
return $this->databaseGraph->getParents($ids, $this->options);
|
return $this->databaseGraph->getParents($ids, $this->options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function runWithCount(): Graph
|
||||||
public
|
|
||||||
function runWithCount(): Graph
|
|
||||||
{
|
{
|
||||||
|
$query = Database::make()->table("lucent_records");
|
||||||
$query = Database::make()->table("records");
|
|
||||||
$query = $this->parseFilters($query);
|
$query = $this->parseFilters($query);
|
||||||
$query = $this->findNotLinked($query);
|
$query = $this->findNotLinked($query);
|
||||||
$graph = $this->run();
|
$graph = $this->run();
|
||||||
@@ -200,15 +226,13 @@ final class Query
|
|||||||
return $graph;
|
return $graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function limit(int $limit): Query
|
public function limit(int $limit): Query
|
||||||
{
|
{
|
||||||
$this->options->limit = $limit;
|
$this->options->limit = $limit;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public function skip(int $skip): Query
|
||||||
function skip(int $skip): Query
|
|
||||||
{
|
{
|
||||||
$this->options->skip = $skip;
|
$this->options->skip = $skip;
|
||||||
return $this;
|
return $this;
|
||||||
@@ -250,7 +274,6 @@ final class Query
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function sort(string $sort): Query
|
public function sort(string $sort): Query
|
||||||
{
|
{
|
||||||
$this->options->sort[] = $sort;
|
$this->options->sort[] = $sort;
|
||||||
@@ -269,12 +292,11 @@ final class Query
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public function orderByQuery(Builder $query): Builder
|
||||||
function orderByQuery(Builder $query): Builder
|
|
||||||
{
|
{
|
||||||
foreach ($this->options->sort as $item) {
|
foreach ($this->options->sort as $item) {
|
||||||
$field = str_replace(".", "->", ltrim($item, '-'));
|
$field = str_replace(".", "->", ltrim($item, "-"));
|
||||||
$dir = str_starts_with($item, '-') ? "desc" : "asc";
|
$dir = str_starts_with($item, "-") ? "desc" : "asc";
|
||||||
if ($field) {
|
if ($field) {
|
||||||
$query->orderBy($field, $dir);
|
$query->orderBy($field, $dir);
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-23
@@ -6,16 +6,13 @@ use Lucent\Database\Database;
|
|||||||
|
|
||||||
class RecordRepo
|
class RecordRepo
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function create(Record $record): void
|
public static function create(Record $record): void
|
||||||
{
|
{
|
||||||
$recordToDB = $record->toDB();
|
$recordToDB = $record->toDB();
|
||||||
|
|
||||||
Database::make()->table("records")->insert($recordToDB);
|
Database::make()->table("lucent_records")->insert($recordToDB);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,40 +20,55 @@ class RecordRepo
|
|||||||
*/
|
*/
|
||||||
public static function updateStatusBulk(Status $status, array $ids): void
|
public static function updateStatusBulk(Status $status, array $ids): void
|
||||||
{
|
{
|
||||||
Database::make()->table("records")->whereIn("id", $ids)->update([
|
Database::make()
|
||||||
'status' => $status->value
|
->table("lucent_records")
|
||||||
]);
|
->whereIn("id", $ids)
|
||||||
|
->update([
|
||||||
|
"status" => $status->value,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function update(Record $record): void
|
public static function update(Record $record): void
|
||||||
{
|
{
|
||||||
$recordToDB = $record->toDB();
|
$recordToDB = $record->toDB();
|
||||||
Database::make()->table("records")->where("id", $record->id)->update($recordToDB);
|
Database::make()
|
||||||
|
->table("lucent_records")
|
||||||
|
->where("id", $record->id)
|
||||||
|
->update($recordToDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $ids
|
* @param string[] $ids
|
||||||
*/
|
*/
|
||||||
public function deleteMany(
|
public function deleteMany(array $ids): void
|
||||||
array $ids,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
|
Database::make()
|
||||||
Database::make()->table("records")->whereIn("id", $ids)->delete();
|
->table("lucent_records")
|
||||||
Database::make()->table("edges")->whereIn("source", $ids)->delete();
|
->whereIn("id", $ids)
|
||||||
Database::make()->table("edges")->whereIn("target", $ids)->delete();
|
->delete();
|
||||||
Database::make()->table("revisions")->whereIn("recordId", $ids)->delete();
|
Database::make()
|
||||||
|
->table("lucent_edges")
|
||||||
|
->whereIn("source", $ids)
|
||||||
|
->delete();
|
||||||
|
Database::make()
|
||||||
|
->table("lucent_edges")
|
||||||
|
->whereIn("target", $ids)
|
||||||
|
->delete();
|
||||||
|
Database::make()
|
||||||
|
->table("lucent_revisions")
|
||||||
|
->whereIn("recordId", $ids)
|
||||||
|
->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteTrashedBySchema(
|
public function deleteTrashedBySchema(string $schemaName): void
|
||||||
string $schemaName,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
$ids = Database::make()->table("records")
|
$ids = Database::make()
|
||||||
|
->table("lucent_records")
|
||||||
->where("schema", $schemaName)
|
->where("schema", $schemaName)
|
||||||
->where("status", Status::TRASHED->value)
|
->where("status", Status::TRASHED->value)
|
||||||
->get()->pluck("id")->toArray();
|
->get()
|
||||||
|
->pluck("id")
|
||||||
|
->toArray();
|
||||||
|
|
||||||
$this->deleteMany($ids);
|
$this->deleteMany($ids);
|
||||||
}
|
}
|
||||||
|
|||||||
+112
-94
@@ -23,20 +23,17 @@ use Lucent\Schema\Validator\ValidatorException;
|
|||||||
|
|
||||||
readonly class RecordService
|
readonly class RecordService
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private AuthService $authService,
|
private AuthService $authService,
|
||||||
private RevisionService $revisionService,
|
private RevisionService $revisionService,
|
||||||
private ChannelService $channelService,
|
private ChannelService $channelService,
|
||||||
private Validator $recordValidator,
|
private Validator $recordValidator,
|
||||||
private Query $query,
|
private Query $query,
|
||||||
private InputFormatter $inputFormatter,
|
private InputFormatter $inputFormatter,
|
||||||
private RecordRepo $recordRepo,
|
private RecordRepo $recordRepo,
|
||||||
private EdgeService $edgeService,
|
private EdgeService $edgeService,
|
||||||
private FileService $fileService,
|
private FileService $fileService,
|
||||||
)
|
) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $url
|
* @param string $url
|
||||||
@@ -47,14 +44,15 @@ readonly class RecordService
|
|||||||
* @throws ValidatorException
|
* @throws ValidatorException
|
||||||
*/
|
*/
|
||||||
public function createFromUrl(
|
public function createFromUrl(
|
||||||
string $url,
|
string $url,
|
||||||
RecordInputData $data,
|
RecordInputData $data,
|
||||||
array $edges
|
array $edges,
|
||||||
): string
|
): string {
|
||||||
{
|
|
||||||
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
||||||
if ($schema->type !== Type::FILES) {
|
if ($schema->type !== Type::FILES) {
|
||||||
throw new LucentException("You can't upload a file to a regular record");
|
throw new LucentException(
|
||||||
|
"You can't upload a file to a regular record",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$fileData = $this->fileService->createFromUrl($schema, $url);
|
$fileData = $this->fileService->createFromUrl($schema, $url);
|
||||||
if ($fileData->isDuplicate) {
|
if ($fileData->isDuplicate) {
|
||||||
@@ -64,14 +62,15 @@ readonly class RecordService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function createFromUploadedFile(
|
public function createFromUploadedFile(
|
||||||
UploadedFile $uploadedFile,
|
UploadedFile $uploadedFile,
|
||||||
RecordInputData $data,
|
RecordInputData $data,
|
||||||
array $edges
|
array $edges,
|
||||||
): string
|
): string {
|
||||||
{
|
|
||||||
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
||||||
if ($schema->type !== Type::FILES) {
|
if ($schema->type !== Type::FILES) {
|
||||||
throw new LucentException("You can't upload a file to a regular record");
|
throw new LucentException(
|
||||||
|
"You can't upload a file to a regular record",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$fileData = $this->fileService->upload($schema, $uploadedFile);
|
$fileData = $this->fileService->upload($schema, $uploadedFile);
|
||||||
if ($fileData->isDuplicate) {
|
if ($fileData->isDuplicate) {
|
||||||
@@ -81,14 +80,15 @@ readonly class RecordService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function createFromFileData(
|
public function createFromFileData(
|
||||||
FileData $fileData,
|
FileData $fileData,
|
||||||
RecordInputData $data,
|
RecordInputData $data,
|
||||||
array $edges
|
array $edges,
|
||||||
): string
|
): string {
|
||||||
{
|
|
||||||
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
$schema = $this->channelService->getSchema($data->schemaName)->get();
|
||||||
if ($schema->type !== Type::FILES) {
|
if ($schema->type !== Type::FILES) {
|
||||||
throw new LucentException("You can't upload a file to a regular record");
|
throw new LucentException(
|
||||||
|
"You can't upload a file to a regular record",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return $this->create($data, $fileData, $edges);
|
return $this->create($data, $fileData, $edges);
|
||||||
}
|
}
|
||||||
@@ -102,12 +102,13 @@ readonly class RecordService
|
|||||||
*/
|
*/
|
||||||
public function create(
|
public function create(
|
||||||
RecordInputData $data,
|
RecordInputData $data,
|
||||||
?FileData $file = null,
|
?FileData $file = null,
|
||||||
array $edges = []
|
array $edges = [],
|
||||||
): string
|
): string {
|
||||||
{
|
$formattedData = $this->inputFormatter->fill(
|
||||||
|
$data->schemaName,
|
||||||
$formattedData = $this->inputFormatter->fill($data->schemaName, new RecordData($data->data));
|
new RecordData($data->data),
|
||||||
|
);
|
||||||
$newRecordId = empty($data->id) ? Id::new() : $data->id;
|
$newRecordId = empty($data->id) ? Id::new() : $data->id;
|
||||||
|
|
||||||
$record = new Record(
|
$record = new Record(
|
||||||
@@ -120,38 +121,49 @@ readonly class RecordService
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($data->status === Status::PUBLISHED) {
|
if ($data->status === Status::PUBLISHED) {
|
||||||
$errors = $this->recordValidator->check($data->schemaName, $record->data);
|
$errors = $this->recordValidator->check(
|
||||||
|
$data->schemaName,
|
||||||
|
$record->data,
|
||||||
|
);
|
||||||
if ($errors->isNotEmpty()) {
|
if ($errors->isNotEmpty()) {
|
||||||
$this->recordValidator->throwException($errors);
|
$this->recordValidator->throwException($errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordRepo::create($record);
|
RecordRepo::create($record);
|
||||||
$newEdges = $this->edgeService->createManyForRecord($record->id, $record->schema, $edges);
|
$newEdges = $this->edgeService->createManyForRecord(
|
||||||
|
$record->id,
|
||||||
|
$record->schema,
|
||||||
|
$edges,
|
||||||
|
);
|
||||||
$this->revisionService->create($record, $newEdges);
|
$this->revisionService->create($record, $newEdges);
|
||||||
return $record->id;
|
return $record->id;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
* @throws ValidatorException
|
* @throws ValidatorException
|
||||||
*/
|
*/
|
||||||
public function update(
|
public function update(string $id, array $data, Status $status): void
|
||||||
string $id,
|
|
||||||
array $data,
|
|
||||||
Status $status,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
$record = $this->query->filter(["id" => $id])->run()->records->first();
|
$record = $this->query
|
||||||
|
->filter(["id" => $id])
|
||||||
|
->run()
|
||||||
|
->records->first();
|
||||||
|
|
||||||
if (empty($record)) {
|
if (empty($record)) {
|
||||||
throw new LucentException("Record id is missing");
|
throw new LucentException("Record id is missing");
|
||||||
}
|
}
|
||||||
$formattedData = $this->inputFormatter->fill($record->schema, new RecordData($data));
|
$formattedData = $this->inputFormatter->fill(
|
||||||
|
$record->schema,
|
||||||
|
new RecordData($data),
|
||||||
|
);
|
||||||
|
|
||||||
if ($status === Status::PUBLISHED) {
|
if ($status === Status::PUBLISHED) {
|
||||||
$errors = $this->recordValidator->check($record->schema, $formattedData);
|
$errors = $this->recordValidator->check(
|
||||||
|
$record->schema,
|
||||||
|
$formattedData,
|
||||||
|
);
|
||||||
if ($errors->isNotEmpty()) {
|
if ($errors->isNotEmpty()) {
|
||||||
$this->recordValidator->throwException($errors);
|
$this->recordValidator->throwException($errors);
|
||||||
}
|
}
|
||||||
@@ -169,7 +181,6 @@ readonly class RecordService
|
|||||||
RecordRepo::update($newRecord);
|
RecordRepo::update($newRecord);
|
||||||
$newEdges = $this->edgeService->findForSource($record->id);
|
$newEdges = $this->edgeService->findForSource($record->id);
|
||||||
$this->revisionService->create($newRecord, $newEdges);
|
$this->revisionService->create($newRecord, $newEdges);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,20 +189,28 @@ readonly class RecordService
|
|||||||
*/
|
*/
|
||||||
public function updateWithEdges(
|
public function updateWithEdges(
|
||||||
string $id,
|
string $id,
|
||||||
array $data,
|
array $data,
|
||||||
Status $status,
|
Status $status,
|
||||||
array $edges
|
array $edges,
|
||||||
): void
|
): void {
|
||||||
{
|
$record = $this->query
|
||||||
$record = $this->query->filter(["id" => $id])->run()->records->first();
|
->filter(["id" => $id])
|
||||||
|
->run()
|
||||||
|
->records->first();
|
||||||
|
|
||||||
if (empty($record)) {
|
if (empty($record)) {
|
||||||
throw new LucentException("Record id is missing");
|
throw new LucentException("Record id is missing");
|
||||||
}
|
}
|
||||||
$formattedData = $this->inputFormatter->fill($record->schema, new RecordData($data));
|
$formattedData = $this->inputFormatter->fill(
|
||||||
|
$record->schema,
|
||||||
|
new RecordData($data),
|
||||||
|
);
|
||||||
|
|
||||||
if ($status === Status::PUBLISHED) {
|
if ($status === Status::PUBLISHED) {
|
||||||
$errors = $this->recordValidator->check($record->schema, $formattedData);
|
$errors = $this->recordValidator->check(
|
||||||
|
$record->schema,
|
||||||
|
$formattedData,
|
||||||
|
);
|
||||||
if ($errors->isNotEmpty()) {
|
if ($errors->isNotEmpty()) {
|
||||||
$this->recordValidator->throwException($errors);
|
$this->recordValidator->throwException($errors);
|
||||||
}
|
}
|
||||||
@@ -207,15 +226,15 @@ readonly class RecordService
|
|||||||
);
|
);
|
||||||
|
|
||||||
RecordRepo::update($newRecord);
|
RecordRepo::update($newRecord);
|
||||||
$newEdges = $this->edgeService->replaceManyForRecord($record->id, $record->schema, $edges);
|
$newEdges = $this->edgeService->replaceManyForRecord(
|
||||||
|
$record->id,
|
||||||
|
$record->schema,
|
||||||
|
$edges,
|
||||||
|
);
|
||||||
$this->revisionService->create($newRecord, $newEdges);
|
$this->revisionService->create($newRecord, $newEdges);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function changeStatusBulk(
|
public function changeStatusBulk(string $status, array $recordsIds): void
|
||||||
string $status,
|
|
||||||
array $recordsIds,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
RecordRepo::updateStatusBulk(Status::from($status), $recordsIds);
|
RecordRepo::updateStatusBulk(Status::from($status), $recordsIds);
|
||||||
}
|
}
|
||||||
@@ -224,10 +243,7 @@ readonly class RecordService
|
|||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
* @throws ValidatorException
|
* @throws ValidatorException
|
||||||
*/
|
*/
|
||||||
public
|
public function clone(string $recordId): string
|
||||||
function clone(
|
|
||||||
string $recordId,
|
|
||||||
): string
|
|
||||||
{
|
{
|
||||||
$graph = $this->query
|
$graph = $this->query
|
||||||
->filter(["id" => $recordId])
|
->filter(["id" => $recordId])
|
||||||
@@ -240,15 +256,18 @@ readonly class RecordService
|
|||||||
throw new LucentException("Record id is missing");
|
throw new LucentException("Record id is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
$newRecordId = (string)Str::uuid();
|
$newRecordId = (string) Str::uuid();
|
||||||
$newEdgesData = $graph->edges
|
$newEdgesData = $graph->edges
|
||||||
->filter(fn(Edge $edge) => $edge->source == $recordId)
|
->filter(fn(Edge $edge) => $edge->source == $recordId)
|
||||||
->values()
|
->values()
|
||||||
->map(fn(Edge $edge) => new EdgeInputData(
|
->map(
|
||||||
target: $edge->target,
|
fn(Edge $edge) => new EdgeInputData(
|
||||||
targetSchema: $edge->targetSchema,
|
target: $edge->target,
|
||||||
field: $edge->field,
|
targetSchema: $edge->targetSchema,
|
||||||
))->toArray();
|
field: $edge->field,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
->toArray();
|
||||||
|
|
||||||
$record->id = $newRecordId;
|
$record->id = $newRecordId;
|
||||||
|
|
||||||
@@ -257,62 +276,62 @@ readonly class RecordService
|
|||||||
schemaName: $record->schema,
|
schemaName: $record->schema,
|
||||||
id: $record->id,
|
id: $record->id,
|
||||||
data: $record->data->toArray(),
|
data: $record->data->toArray(),
|
||||||
status: Status::DRAFT
|
status: Status::DRAFT,
|
||||||
),
|
),
|
||||||
file: $record->_file,
|
file: $record->_file,
|
||||||
edges: $newEdgesData
|
edges: $newEdgesData,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deleteMany(array $recordsIds): void
|
||||||
public function deleteMany(
|
|
||||||
array $recordsIds,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
$this->recordRepo->deleteMany($recordsIds);
|
$this->recordRepo->deleteMany($recordsIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function emptyTrash(
|
public function emptyTrash(string $schemaName): void
|
||||||
string $schemaName,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
|
|
||||||
$schema = $this->channelService->getSchema($schemaName)->get();
|
$schema = $this->channelService->getSchema($schemaName)->get();
|
||||||
$this->recordRepo->deleteTrashedBySchema($schemaName);
|
$this->recordRepo->deleteTrashedBySchema($schemaName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws LucentException
|
* @throws LucentException
|
||||||
* @throws ValidatorException
|
* @throws ValidatorException
|
||||||
*/
|
*/
|
||||||
public function rollback(
|
public function rollback(string $recordId, int $version): void
|
||||||
string $recordId,
|
|
||||||
int $version,
|
|
||||||
): void
|
|
||||||
{
|
{
|
||||||
$revision = $this->revisionService->getByRecordIdAndVersion($recordId, $version)->get();
|
$revision = $this->revisionService
|
||||||
|
->getByRecordIdAndVersion($recordId, $version)
|
||||||
|
->get();
|
||||||
$this->updateWithEdges(
|
$this->updateWithEdges(
|
||||||
id: $revision->recordId,
|
id: $revision->recordId,
|
||||||
data: $revision->data->toArray(),
|
data: $revision->data->toArray(),
|
||||||
status: Status::DRAFT,
|
status: Status::DRAFT,
|
||||||
edges: array_map(fn(Edge $edge) => new EdgeInputData($edge->target, $edge->targetSchema, $edge->field), $revision->_edges),
|
edges: array_map(
|
||||||
|
fn(Edge $edge) => new EdgeInputData(
|
||||||
|
$edge->target,
|
||||||
|
$edge->targetSchema,
|
||||||
|
$edge->field,
|
||||||
|
),
|
||||||
|
$revision->_edges,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createEmpty(Schema $schema): Record
|
||||||
public function createEmpty(
|
|
||||||
Schema $schema,
|
|
||||||
): Record
|
|
||||||
{
|
{
|
||||||
|
$defaultValues = $schema->fields->reduce(function (
|
||||||
$defaultValues = $schema->fields->reduce(function ($carry, FieldInterface $f) {
|
$carry,
|
||||||
|
FieldInterface $f,
|
||||||
|
) {
|
||||||
$carry[$f->name] = $f->default ?? null;
|
$carry[$f->name] = $f->default ?? null;
|
||||||
return $carry;
|
return $carry;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
$formattedData = $this->inputFormatter->fill($schema->name, new RecordData($defaultValues));
|
$formattedData = $this->inputFormatter->fill(
|
||||||
|
$schema->name,
|
||||||
|
new RecordData($defaultValues),
|
||||||
|
);
|
||||||
|
|
||||||
return new Record(
|
return new Record(
|
||||||
id: Id::new(),
|
id: Id::new(),
|
||||||
@@ -322,6 +341,5 @@ readonly class RecordService
|
|||||||
data: $formattedData,
|
data: $formattedData,
|
||||||
_file: null,
|
_file: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use stdClass;
|
|||||||
class RevisionRepo
|
class RevisionRepo
|
||||||
{
|
{
|
||||||
|
|
||||||
private string $table = "revisions";
|
private string $table = "lucent_revisions";
|
||||||
|
|
||||||
public function create(Revision $revision): string
|
public function create(Revision $revision): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,53 +8,23 @@ use Lucent\Setup\Data\SetupStep;
|
|||||||
|
|
||||||
class DatabaseSetupStep implements IStep
|
class DatabaseSetupStep implements IStep
|
||||||
{
|
{
|
||||||
|
|
||||||
public function __invoke(): SetupStep
|
public function __invoke(): SetupStep
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
$name = "Database Connection";
|
$name = "Database Connection";
|
||||||
|
|
||||||
$databaseConnectionName = config("lucent.database");
|
|
||||||
|
|
||||||
|
|
||||||
$databaseConfig = config('database.connections.'.$databaseConnectionName);
|
|
||||||
|
|
||||||
if (empty($databaseConfig)) {
|
|
||||||
$instructions = <<<EOD
|
|
||||||
# You need to setup a database connection for $databaseConnectionName in database.php config. You can choose either sqlite or pgsql
|
|
||||||
# example:
|
|
||||||
|
|
||||||
'connections' => [
|
|
||||||
'$databaseConnectionName' => [
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'url' => env('LUCENT_DATABASE_URL'),
|
|
||||||
'database' => env('LUCENT_DB_DATABASE', database_path('database.sqlite')),
|
|
||||||
'prefix' => '',
|
|
||||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
|
||||||
],
|
|
||||||
EOD;
|
|
||||||
return SetupStep::makeFail($name, $instructions);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(!in_array($driver = $databaseConfig["driver"],["sqlite","pgsql"])){
|
|
||||||
$instructions = <<<EOD
|
|
||||||
You can choose either sqlite or pgsql. You current config is: $driver
|
|
||||||
EOD;
|
|
||||||
return SetupStep::makeFail($name, $instructions);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Database::make()->table("records")->get();
|
Database::make()->table("lucent_records")->get();
|
||||||
} catch (QueryException $e) {
|
} catch (QueryException $e) {
|
||||||
$instructions = <<<EOD
|
$instructions = <<<EOD
|
||||||
# Make sure you run:
|
# Make sure you run:
|
||||||
php artisan lucent:setup-db
|
php artisan lucent:setup-db
|
||||||
EOD;
|
EOD;
|
||||||
return SetupStep::makeFail($name, $instructions);
|
return SetupStep::makeFail($name, $instructions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SetupStep::makeSuccess($name, "Database Connection successfully created");
|
return SetupStep::makeSuccess(
|
||||||
|
$name,
|
||||||
|
"Database Connection successfully created",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-21
@@ -5,44 +5,54 @@ namespace Lucent\Svelte;
|
|||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Lucent\Account\AccountService;
|
use Lucent\Account\AccountService;
|
||||||
|
use Lucent\Account\AuthService;
|
||||||
use Lucent\Channel\ChannelService;
|
use Lucent\Channel\ChannelService;
|
||||||
|
|
||||||
class Svelte
|
class Svelte
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public ChannelService $channelService,
|
public ChannelService $channelService,
|
||||||
public AccountService $accountService
|
public AccountService $accountService,
|
||||||
)
|
public AuthService $authService,
|
||||||
{
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
function render(string $layout, string $view, string $title = "", mixed $data = []): View|Factory
|
|
||||||
{
|
|
||||||
|
|
||||||
|
function render(
|
||||||
|
string $layout,
|
||||||
|
string $view,
|
||||||
|
string $title = "",
|
||||||
|
mixed $data = [],
|
||||||
|
): View|Factory {
|
||||||
$context = [];
|
$context = [];
|
||||||
$context["user"] = session('user');
|
$context["user"] = toArray($this->authService->getCurrentUser());
|
||||||
$context["view"] = $view;
|
$context["view"] = $view;
|
||||||
$context["layout"] = $layout;
|
$context["layout"] = $layout;
|
||||||
$context["title"] = $title;
|
$context["title"] = $title;
|
||||||
$context["data"] = $data;
|
$context["data"] = $data;
|
||||||
$context["channel"] = $this->channelService->channel;
|
$context["channel"] = $this->channelService->channel;
|
||||||
$context["readableSchemas"] = $this->accountService->currentReadableSchemas();
|
$context[
|
||||||
|
"readableSchemas"
|
||||||
|
] = $this->accountService->currentReadableSchemas();
|
||||||
$json = json_encode($context);
|
$json = json_encode($context);
|
||||||
|
|
||||||
$divTag = sprintf('<div class="lucent-component" data-layout="%s"></div>', $layout);
|
$divTag = sprintf(
|
||||||
$jsonTag = sprintf('<script type="application/json" id="json-%s">%s</script>', $layout, $json);
|
'<div class="lucent-component" data-layout="%s"></div>',
|
||||||
|
$layout,
|
||||||
|
);
|
||||||
|
$jsonTag = sprintf(
|
||||||
|
'<script type="application/json" id="json-%s">%s</script>',
|
||||||
|
$layout,
|
||||||
|
$json,
|
||||||
|
);
|
||||||
|
|
||||||
$svelte = $divTag . $jsonTag;
|
$svelte = $divTag . $jsonTag;
|
||||||
|
|
||||||
return view('lucent::svelte', [
|
return view("lucent::svelte", [
|
||||||
'svelte' => $svelte,
|
"svelte" => $svelte,
|
||||||
'view' => $view,
|
"view" => $view,
|
||||||
'data' => $data,
|
"data" => $data,
|
||||||
'title' => $title,
|
"title" => $title,
|
||||||
'layout' => $layout,
|
"layout" => $layout,
|
||||||
'channel' => $this->channelService->channel,
|
"channel" => $this->channelService->channel,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user