permissions
This commit is contained in:
@@ -23,9 +23,11 @@
|
||||
export let data;
|
||||
// export let layout;
|
||||
export let channel;
|
||||
export let readableSchemas;
|
||||
|
||||
|
||||
setContext("channel", channel);
|
||||
setContext("readableSchemas", channel.schemas.filter((s) => readableSchemas.includes(s.name)));
|
||||
setContext("user", user);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
export let schema;
|
||||
const channel = getContext("channel");
|
||||
const readableSchemas = getContext("readableSchemas");
|
||||
const user = getContext("user");
|
||||
|
||||
let contentIsOpen = false;
|
||||
const fileSchemas = channel.schemas.filter((sc) => sc.type === "files");
|
||||
const otherSchemas = channel.schemas.filter((sc) => !sc.isEntry && sc.type === "collection");
|
||||
const fileSchemas = readableSchemas.filter((sc) => sc.type === "files");
|
||||
const otherSchemas = readableSchemas.filter((sc) => !sc.isEntry && sc.type === "collection");
|
||||
|
||||
</script>
|
||||
|
||||
@@ -57,7 +58,7 @@
|
||||
aria-labelledby="panelsStayOpen-headingMain">
|
||||
<div class="accordion-body">
|
||||
<NavbarMenu
|
||||
schemas={ channel.schemas.filter((sc) => sc.isEntry)}
|
||||
schemas={ readableSchemas.filter((sc) => sc.isEntry)}
|
||||
schema={schema}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export let inModal;
|
||||
export let modalUrl;
|
||||
export let selected = [];
|
||||
export let isWritable = false;
|
||||
|
||||
|
||||
function selectRecord(e, record) {
|
||||
@@ -62,68 +63,72 @@
|
||||
<h3 class="header-normal mb-5 ">
|
||||
{schema.label}
|
||||
</h3>
|
||||
{#if selected.length > 0 && !inModal}
|
||||
{#if selected.length > 0 && !inModal && isWritable}
|
||||
<ActionsOnSelected {schema} {selected} {inModal} {filter}/>
|
||||
{:else}
|
||||
<Tools
|
||||
bind:schema
|
||||
bind:records
|
||||
{systemFields}
|
||||
{sort}
|
||||
{operators}
|
||||
{filter}
|
||||
{inModal}
|
||||
{modalUrl}
|
||||
on:refresh={refresh}
|
||||
bind:schema
|
||||
bind:records
|
||||
{systemFields}
|
||||
{sort}
|
||||
{operators}
|
||||
{filter}
|
||||
{inModal}
|
||||
{modalUrl}
|
||||
{isWritable}
|
||||
on:refresh={refresh}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if schema.type === "collection"}
|
||||
<Table
|
||||
{records}
|
||||
{graph}
|
||||
{schema}
|
||||
{sort}
|
||||
{systemFields}
|
||||
{inModal}
|
||||
{users}
|
||||
bind:selected
|
||||
{records}
|
||||
{graph}
|
||||
{schema}
|
||||
{sort}
|
||||
{systemFields}
|
||||
{inModal}
|
||||
{users}
|
||||
{isWritable}
|
||||
bind:selected
|
||||
/>
|
||||
{:else}
|
||||
<div class="row" style="max-width:1000px">
|
||||
{#each records as record (record.id)}
|
||||
<div class="col-6 col-md-4">
|
||||
<div
|
||||
class="file-wrapper rounded p-2 mb-4 bg-light"
|
||||
class:selected={selected.includes(record)}
|
||||
class="file-wrapper rounded p-2 mb-4 bg-light"
|
||||
class:selected={selected.includes(record)}
|
||||
>
|
||||
<div class="form-check">
|
||||
<input
|
||||
on:change={(e) => selectRecord(e, record)}
|
||||
class="form-check-input "
|
||||
type="checkbox"
|
||||
checked={selected.find(
|
||||
{#if isWritable}
|
||||
<div class="form-check">
|
||||
<input
|
||||
on:change={(e) => selectRecord(e, record)}
|
||||
class="form-check-input "
|
||||
type="checkbox"
|
||||
checked={selected.find(
|
||||
(r) => r.id === record.id
|
||||
)}
|
||||
value={record}
|
||||
/>
|
||||
</div>
|
||||
value={record}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="d-flex justify-content-center">
|
||||
<Preview {record} size="medium"/>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="{channel.lucentUrl}/records/{record.id}"
|
||||
title={record._file.path}
|
||||
class="d-block text-center overflow-hidden text-nowrap my-2 "
|
||||
style="
|
||||
href="{channel.lucentUrl}/records/{record.id}"
|
||||
title={record._file.path}
|
||||
class="d-block text-center overflow-hidden text-nowrap my-2 "
|
||||
style="
|
||||
text-overflow: ellipsis;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
">{record._file.path}</a
|
||||
>
|
||||
<span
|
||||
class="lx-small-text text-muted d-block text-center"
|
||||
class="lx-small-text text-muted d-block text-center"
|
||||
>{record._file.mime}</span
|
||||
>
|
||||
</div>
|
||||
@@ -134,12 +139,12 @@
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
{limit}
|
||||
{skip}
|
||||
{total}
|
||||
on:refresh={refresh}
|
||||
{inModal}
|
||||
{modalUrl}
|
||||
{limit}
|
||||
{skip}
|
||||
{total}
|
||||
on:refresh={refresh}
|
||||
{inModal}
|
||||
{modalUrl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
export let systemFields;
|
||||
export let sort;
|
||||
export let inModal;
|
||||
export let isWritable;
|
||||
export let selected = [];
|
||||
|
||||
function toggleAll(e) {
|
||||
@@ -44,16 +45,18 @@
|
||||
<table class="">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>
|
||||
<input
|
||||
on:change|preventDefault={toggleAll}
|
||||
indeterminate={selected.length > 0 &&
|
||||
{#if isWritable}
|
||||
<th>
|
||||
<input
|
||||
on:change|preventDefault={toggleAll}
|
||||
indeterminate={selected.length > 0 &&
|
||||
selected.length < records.length}
|
||||
checked={selected.length == records.length}
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</th>
|
||||
checked={selected.length == records.length}
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</th>
|
||||
{/if}
|
||||
|
||||
{#each visibleColumns as field}
|
||||
<th
|
||||
@@ -79,18 +82,20 @@
|
||||
class="title-td-contents d-inline-flex justify-content-between w-100 align-items-center"
|
||||
>
|
||||
<div class="d-flex align-items-center ">
|
||||
<div class="form-check">
|
||||
<input
|
||||
on:change={(e) =>
|
||||
{#if isWritable}
|
||||
<div class="form-check">
|
||||
<input
|
||||
on:change={(e) =>
|
||||
selectRecord(e, record)}
|
||||
class="form-check-input "
|
||||
type="checkbox"
|
||||
checked={selected.find(
|
||||
class="form-check-input "
|
||||
type="checkbox"
|
||||
checked={selected.find(
|
||||
(r) => r.id === record.id
|
||||
)}
|
||||
value={record}
|
||||
/>
|
||||
</div>
|
||||
value={record}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<a
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
export let filter;
|
||||
export let inModal;
|
||||
export let modalUrl;
|
||||
export let isWritable;
|
||||
export let records;
|
||||
export let systemFields = [];
|
||||
// export let visibleFields = [];
|
||||
@@ -83,7 +84,7 @@
|
||||
|
||||
<div class="d-flex align-items-center ">
|
||||
{#if schema.type === "collection"}
|
||||
{#if !inModal}
|
||||
{#if !inModal && isWritable}
|
||||
<a
|
||||
href="{channel.lucentUrl}/records/new?schema={schema.name}"
|
||||
class="btn btn-sm btn-primary"
|
||||
@@ -109,14 +110,16 @@
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
{#if filter["status_in"] === "trashed"}
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="{channel.lucentUrl}/content/{schema.name}/emptyTrash"
|
||||
>
|
||||
Empty trash
|
||||
</a>
|
||||
</li>
|
||||
{#if isWritable}
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="{channel.lucentUrl}/content/{schema.name}/emptyTrash"
|
||||
>
|
||||
Empty trash
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
<li>
|
||||
|
||||
@@ -7,11 +7,26 @@
|
||||
export let member;
|
||||
export let roles;
|
||||
|
||||
function update(e, newRole) {
|
||||
|
||||
function removeFrom(e, aRole) {
|
||||
e.preventDefault();
|
||||
let newRoles = member.roles.filter((r) => r !== aRole);
|
||||
dispatch("update", {
|
||||
user: member.id,
|
||||
role: newRole,
|
||||
roles: newRoles,
|
||||
});
|
||||
}
|
||||
|
||||
function addTo(e, aRole) {
|
||||
e.preventDefault();
|
||||
|
||||
let newRoles = [...member.roles, aRole];
|
||||
console.log(member.roles)
|
||||
console.log(aRole)
|
||||
console.log(newRoles)
|
||||
dispatch("update", {
|
||||
user: member.id,
|
||||
roles: newRoles,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,11 +34,11 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
transition:fly={{ duration: 200 }}
|
||||
class="d-flex justify-content-between align-items-center mb-3 "
|
||||
transition:fly={{ duration: 200 }}
|
||||
class="d-flex justify-content-between align-items-center mb-3 "
|
||||
>
|
||||
<div class="d-flex align-items-center status-{member.role}">
|
||||
<Avatar name={member.name ?? "" } side="32"/>
|
||||
<div class="d-flex align-items-center status-{member.roles.includes('removed') ? 'removed' : 'active'}">
|
||||
<Avatar name={member.name ?? "" } side={32}/>
|
||||
<div class="ms-3 ">
|
||||
<div>
|
||||
<span class="fs-5">
|
||||
@@ -36,21 +51,37 @@
|
||||
<div>
|
||||
<div class="dropdown dropdown-center">
|
||||
<button
|
||||
class=" dropdown-toggle btn btn-light"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
class=" dropdown-toggle btn btn-light"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{member.role}
|
||||
Roles
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<h6 class="dropdown-header">Remove role</h6>
|
||||
{#each roles as role}
|
||||
{#if member.role !== role}
|
||||
{#if member.roles.includes(role)}
|
||||
<button
|
||||
class="dropdown-item"
|
||||
on:click={(e) => update(e,role)}
|
||||
class="dropdown-item text-capitalize"
|
||||
on:click={(e) => removeFrom(e,role)}
|
||||
>
|
||||
Convert to {role}
|
||||
{role}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
<div>
|
||||
<hr class="dropdown-divider">
|
||||
</div>
|
||||
|
||||
<h6 class="dropdown-header">Add role</h6>
|
||||
{#each roles as role}
|
||||
{#if !member.roles.includes(role)}
|
||||
<button
|
||||
class="dropdown-item text-capitalize"
|
||||
on:click={(e) => addTo(e,role)}
|
||||
>
|
||||
{role}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
const channel = getContext("channel");
|
||||
export let users;
|
||||
export let roles;
|
||||
let name;
|
||||
let email;
|
||||
let role;
|
||||
@@ -28,7 +27,7 @@
|
||||
.post(channel.lucentUrl + "/members/invite", {
|
||||
name: newName,
|
||||
email: newEmail,
|
||||
role: newRole,
|
||||
roles: [newRole],
|
||||
})
|
||||
.then((response) => {
|
||||
successAlert.show("User was invited");
|
||||
@@ -49,7 +48,7 @@
|
||||
axios
|
||||
.post(channel.lucentUrl + "/members/update", {
|
||||
id: e.detail.user,
|
||||
role: e.detail.role,
|
||||
roles: e.detail.roles,
|
||||
})
|
||||
.then((response) => {
|
||||
successAlert.show("Users updated");
|
||||
@@ -96,7 +95,7 @@
|
||||
</div>
|
||||
|
||||
<div class="me-3">
|
||||
{#each roles.filter((r) => r !== "removed") as arole}
|
||||
{#each channel.roles.filter((r) => r !== "removed") as arole}
|
||||
<Radio
|
||||
bind:group={role}
|
||||
value={arole}
|
||||
@@ -117,7 +116,7 @@
|
||||
{#each users as user}
|
||||
<MemberSettingsCard
|
||||
member={user}
|
||||
roles={roles}
|
||||
roles={channel.roles}
|
||||
on:update={update}
|
||||
on:reinvite={(e) => invite(e.detail.email, e.detail.role)}
|
||||
/>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
};
|
||||
export let recordHistory;
|
||||
export let isCreateMode;
|
||||
export let isWritable = false;
|
||||
export let users;
|
||||
let originalContent;
|
||||
let activeContentTab = "";
|
||||
@@ -155,7 +156,7 @@
|
||||
<Manager managerRecords={recordHistory} {graph}/>
|
||||
<EditHeader {schema} {record} {isCreateMode} {graph} bind:activeContentTab/>
|
||||
|
||||
{#if !["_graph", "_info"].includes(activeContentTab)}
|
||||
{#if !["_graph", "_info"].includes(activeContentTab) && isWritable}
|
||||
<div class="shadow-lg "
|
||||
style="position:fixed;bottom:0;left:0px;width:100%;background: rgb(206, 223, 210);z-index:1050"
|
||||
>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
export let title;
|
||||
</script>
|
||||
|
||||
|
||||
<div class="wrapper-normal ">
|
||||
<div class="header-normal">
|
||||
Record Not Found
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
|
||||
namespace Lucent\Account;
|
||||
|
||||
use Lucent\Channel\ChannelService;
|
||||
use Lucent\Primitive\Collection;
|
||||
use Lucent\Schema\SchemaService;
|
||||
|
||||
readonly class AccountService
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private UserRepo $userRepo,
|
||||
private AuthService $authService,
|
||||
private ChannelService $channelService,
|
||||
private SchemaService $schemaService,
|
||||
private UserRepo $userRepo,
|
||||
)
|
||||
{
|
||||
|
||||
@@ -37,5 +42,20 @@ readonly class AccountService
|
||||
return $this->all()->map(fn($user) => $user->safe());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
public function currentReadableSchemas(): array
|
||||
{
|
||||
$roles = $this->authService->currentUserRoles();
|
||||
return $this->channelService->schemasReadableByRoles($roles);
|
||||
}
|
||||
|
||||
public function currentWritableSchemas(): array
|
||||
{
|
||||
$roles = $this->authService->currentUserRoles();
|
||||
return $this->channelService->schemasWritableByRoles($roles);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+27
-14
@@ -25,14 +25,19 @@ readonly class AuthService
|
||||
|
||||
public function currentUserId(): ?string
|
||||
{
|
||||
if(app()->runningInConsole()){
|
||||
if (app()->runningInConsole()) {
|
||||
return config("lucent.systemUserId");
|
||||
}else{
|
||||
} 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());
|
||||
@@ -50,7 +55,7 @@ readonly class AuthService
|
||||
throw new LucentException("You account was not found");
|
||||
}
|
||||
|
||||
if ($user->get()->role === Role::REMOVED) {
|
||||
if ($user->get()->isRemoved()) {
|
||||
throw new LucentException("Your account is not active");
|
||||
}
|
||||
|
||||
@@ -66,18 +71,17 @@ readonly class AuthService
|
||||
$newUser->updatedAt = Carbon::now()->toJson();
|
||||
$newUser->mailToken = null;
|
||||
$this->userRepo->update($newUser);
|
||||
|
||||
$this->session->put(["user" => $user->get()->safe()]);
|
||||
}
|
||||
|
||||
|
||||
public function create(string $name, string $email, string $role): User
|
||||
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),
|
||||
role: Role::from($role),
|
||||
roles: $this->validateRoles($roles),
|
||||
createdAt: Carbon::now()->toJson(),
|
||||
updatedAt: Carbon::now()->toJson(),
|
||||
loggedInAt: Carbon::now()->toJson(),
|
||||
@@ -101,7 +105,7 @@ readonly class AuthService
|
||||
throw new LucentException("User not found");
|
||||
}
|
||||
|
||||
if ($user->get()->role === Role::REMOVED) {
|
||||
if ($user->get()->isRemoved()) {
|
||||
throw new LucentException("Cannot reset email if the user is not active");
|
||||
}
|
||||
|
||||
@@ -121,7 +125,7 @@ readonly class AuthService
|
||||
/**
|
||||
* @throws LucentException
|
||||
*/
|
||||
public function changeRole(string $userId, string $newRole): void
|
||||
public function changeRoles(string $userId, array $roles): void
|
||||
{
|
||||
$user = $this->userRepo->findById($userId);
|
||||
|
||||
@@ -130,7 +134,7 @@ readonly class AuthService
|
||||
}
|
||||
|
||||
$newUser = $user->get();
|
||||
$newUser->role = Role::from($newRole);
|
||||
$newUser->roles = $this->validateRoles($roles);
|
||||
$newUser->updatedAt = Carbon::now()->toJson();
|
||||
$this->userRepo->update($newUser);
|
||||
}
|
||||
@@ -138,7 +142,7 @@ readonly class AuthService
|
||||
/**
|
||||
* @throws LucentException
|
||||
*/
|
||||
public function updateName( string $name): void
|
||||
public function updateName(string $name): void
|
||||
{
|
||||
$name = (new Name($name));
|
||||
$this->userRepo->updateName($this->currentUserId(), $name);
|
||||
@@ -153,7 +157,7 @@ readonly class AuthService
|
||||
{
|
||||
$email = (new Email($email));
|
||||
$user = $this->userRepo->findByEmail($email);
|
||||
if($user->isDefined()){
|
||||
if ($user->isDefined()) {
|
||||
throw new LucentException("Email already assigned to user");
|
||||
}
|
||||
|
||||
@@ -169,10 +173,10 @@ readonly class AuthService
|
||||
public function invite(
|
||||
string $name,
|
||||
string $email,
|
||||
string $role
|
||||
array $roles
|
||||
): User
|
||||
{
|
||||
$user = $this->create($name, $email, $role);
|
||||
$user = $this->create($name, $email, $roles);
|
||||
$this->sendLoginEmail($user->email);
|
||||
return $user;
|
||||
}
|
||||
@@ -185,10 +189,19 @@ readonly class AuthService
|
||||
string $email
|
||||
): User
|
||||
{
|
||||
$user = $this->invite($name, $email, "admin");
|
||||
$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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Account;
|
||||
|
||||
enum Role: string
|
||||
{
|
||||
case ADMIN = 'admin';
|
||||
case EDITOR = 'editor';
|
||||
case READER = 'reader';
|
||||
case REMOVED = 'removed';
|
||||
|
||||
function hasAccess(string $roleName): bool
|
||||
{
|
||||
$trialRole = Role::from($roleName);
|
||||
|
||||
$access = match ($trialRole) {
|
||||
Role::ADMIN => [Role::ADMIN, Role::DEVELOPER, Role::EDITOR, Role::READER],
|
||||
Role::EDITOR => [Role::EDITOR, Role::READER],
|
||||
Role::READER => [Role::READER],
|
||||
Role::REMOVED => [],
|
||||
};
|
||||
|
||||
return in_array($trialRole, $access);
|
||||
}
|
||||
}
|
||||
+9
-15
@@ -6,11 +6,14 @@ namespace Lucent\Account;
|
||||
class User
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array<string> $roles
|
||||
*/
|
||||
function __construct(
|
||||
public string $id,
|
||||
public Name $name,
|
||||
public Email $email,
|
||||
public Role $role,
|
||||
public array $roles,
|
||||
public string $createdAt,
|
||||
public string $updatedAt,
|
||||
public ?string $loggedInAt = null,
|
||||
@@ -20,24 +23,15 @@ class User
|
||||
{
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): User
|
||||
{
|
||||
return new User(
|
||||
id: $data["id"],
|
||||
name: new Name($data["name"] ?? ""),
|
||||
email: new Email($data["email"]),
|
||||
role: Role::tryFrom($data["role"]),
|
||||
createdAt: $data["createdAt"],
|
||||
updatedAt: $data["updatedAt"],
|
||||
loggedInAt: $data["loggedInAt"] ?? null,
|
||||
mailToken: $data["mailToken"] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
public function isRemoved()
|
||||
{
|
||||
return in_array("removed", $this->roles);
|
||||
}
|
||||
|
||||
public function safe(): array
|
||||
{
|
||||
$userData = collect(toArray($this));
|
||||
return $userData->only(["id", "name", "email", "role", "status"])->toArray();
|
||||
return $userData->only(["id", "name", "email", "roles", "status"])->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,31 +9,10 @@ readonly class UserProfile
|
||||
public string $id,
|
||||
public string $name,
|
||||
public string $email,
|
||||
public Role $role,
|
||||
public array $roles,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public static function fromUser(User $user): UserProfile
|
||||
{
|
||||
return new UserProfile(
|
||||
$user->id,
|
||||
$user->name,
|
||||
$user->email,
|
||||
$user->role,
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): ?UserProfile
|
||||
{
|
||||
if (empty($data)) {
|
||||
return null;
|
||||
}
|
||||
return new UserProfile(
|
||||
id: data_get($data, "id"),
|
||||
name: data_get($data, "name"),
|
||||
email: data_get($data, "email"),
|
||||
role: data_get($data, "role"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class UserRepo
|
||||
{
|
||||
$usersData = DB::table("users")->get();
|
||||
|
||||
$users = array_map(fn($userData) => User::fromArray((array)$userData), $usersData->toArray());
|
||||
$users = array_map(fn($userData) => $this->fromArray((array)$userData), $usersData->toArray());
|
||||
return new Collection($users);
|
||||
}
|
||||
|
||||
@@ -30,12 +30,14 @@ class UserRepo
|
||||
public static function insert(User $user): void
|
||||
{
|
||||
$userData = toArray($user);
|
||||
$userData["roles"] = json_encode($userData["roles"]);
|
||||
DB::table("users")->insert($userData);
|
||||
}
|
||||
|
||||
public function update(User $user): void
|
||||
{
|
||||
$userData = toArray($user);
|
||||
$userData["roles"] = json_encode($userData["roles"]);
|
||||
DB::table("users")->where("id", $user->id)->update($userData);
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ class UserRepo
|
||||
return none();
|
||||
}
|
||||
|
||||
return some(User::fromArray((array)$user));
|
||||
return some($this->fromArray((array)$user));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +82,7 @@ class UserRepo
|
||||
return none();
|
||||
}
|
||||
|
||||
return some(User::fromArray((array)$user));
|
||||
return some($this->fromArray((array)$user));
|
||||
}
|
||||
|
||||
|
||||
@@ -93,4 +95,18 @@ class UserRepo
|
||||
{
|
||||
DB::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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ final class Channel
|
||||
public string $generateCommand,
|
||||
public Collection $schemas,
|
||||
public array $imageFilters,
|
||||
public array $roles,
|
||||
)
|
||||
{
|
||||
$this->lucentUrl = $url . "/lucent";
|
||||
|
||||
@@ -22,24 +22,23 @@ final class ChannelService
|
||||
public static function fromConfig(): ChannelService
|
||||
{
|
||||
$schemasArray = [];
|
||||
if(file_exists(schemas_path())){
|
||||
if (file_exists(schemas_path())) {
|
||||
$schemasJson = file_get_contents(schemas_path());
|
||||
$schemasArray = json_decode($schemasJson, true);
|
||||
}
|
||||
|
||||
|
||||
$schemaService = new SchemaService();
|
||||
$schemasCollection = (new Collection($schemasArray))->map([$schemaService, 'fromArray']);
|
||||
|
||||
$schemasCollection = (new Collection($schemasArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
|
||||
|
||||
$channel = new Channel(
|
||||
name: config("lucent.name") ?? "",
|
||||
url: rtrim( config("lucent.url") ?? "", "/"),
|
||||
previewTarget: rtrim( config("lucent.previewTarget") ?? "", "/"),
|
||||
url: rtrim(config("lucent.url") ?? "", "/"),
|
||||
previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"),
|
||||
generateCommand: config("lucent.generateCommand") ?? "",
|
||||
schemas: $schemasCollection,
|
||||
imageFilters: config("lucent.imageFilters") ?? [],
|
||||
roles: $schemasArray["roles"] ?? []
|
||||
);
|
||||
|
||||
$channelService = new ChannelService($schemaService);
|
||||
$channelService->channel = $channel;
|
||||
return $channelService;
|
||||
@@ -58,4 +57,28 @@ final class ChannelService
|
||||
return some($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $roles
|
||||
* @return array<string>
|
||||
*/
|
||||
public function schemasReadableByRoles(array $roles): array
|
||||
{
|
||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->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();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $roles
|
||||
* @return array<string>
|
||||
*/
|
||||
public function schemasWritableByRoles(array $roles): array
|
||||
{
|
||||
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->write))->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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Lucent\Commands;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Illuminate\Console\Command;
|
||||
use Lucent\Schema\Schema;
|
||||
use Lucent\Schema\SchemaService;
|
||||
use Lucent\Schema\Type;
|
||||
|
||||
class CompileSchemas extends Command
|
||||
{
|
||||
@@ -13,7 +16,9 @@ class CompileSchemas extends Command
|
||||
protected $description = 'Compiles schemas';
|
||||
|
||||
|
||||
public function __construct()
|
||||
public function __construct(
|
||||
public SchemaService $schemaService
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -39,13 +44,28 @@ class CompileSchemas extends Command
|
||||
|
||||
}
|
||||
|
||||
$schemas = collect($schemas)->sortBy("label")->values()->toArray();
|
||||
$schemas = collect($schemas)->sortBy("label")->values();
|
||||
$roles = $schemas
|
||||
->map([$this->schemaService, 'fromArray'])
|
||||
->whereIn("type", [Type::COLLECTION, Type::FILES])
|
||||
->reduce(fn($carry, Schema $schema) => array_merge(
|
||||
$carry,
|
||||
$schema->read,
|
||||
$schema->write,
|
||||
config("lucent.canInvite"),
|
||||
config("lucent.canBuild"),
|
||||
), []);
|
||||
|
||||
if(!file_exists(storage_path("lucent"))){
|
||||
$json = [
|
||||
"schemas" => $schemas->toArray(),
|
||||
"roles" => collect($roles)->push("admin")->push("removed")->unique()->values()->toArray()
|
||||
];
|
||||
|
||||
if (!file_exists(storage_path("lucent"))) {
|
||||
mkdir(storage_path("lucent"));
|
||||
}
|
||||
|
||||
file_put_contents(schemas_path(), json_encode($schemas));
|
||||
file_put_contents(schemas_path(), json_encode($json));
|
||||
|
||||
$this->info("Lucent Schemas were updated");
|
||||
}
|
||||
|
||||
+3
-1
@@ -7,5 +7,7 @@ return [
|
||||
"url" => env("LUCENT_URL", env('APP_URL')),
|
||||
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
|
||||
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
|
||||
"imageFilters" => []
|
||||
"imageFilters" => [],
|
||||
"canInvite" => ["admin"],
|
||||
"canBuild" => ["admin"],
|
||||
];
|
||||
|
||||
@@ -16,7 +16,7 @@ return new class extends Migration {
|
||||
$table->uuid("id")->primary();
|
||||
$table->string('name')->nullable();
|
||||
$table->string('email')->unique();
|
||||
$table->string('role');
|
||||
$table->jsonb('roles');
|
||||
$table->string('createdAt');
|
||||
$table->string('updatedAt');
|
||||
$table->string('loggedInAt');
|
||||
|
||||
@@ -7,8 +7,6 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Lucent\Account\AccountService;
|
||||
use Lucent\Account\UserRepo;
|
||||
use Lucent\Channel\ChannelService;
|
||||
use Lucent\Query\Query;
|
||||
use Lucent\Svelte\Svelte;
|
||||
use function Lucent\Response\ok;
|
||||
@@ -17,7 +15,7 @@ class HomeController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Svelte $svelte,
|
||||
private readonly AccountService $accountService,
|
||||
private readonly AccountService $accountService,
|
||||
private readonly Query $query,
|
||||
)
|
||||
{
|
||||
@@ -36,10 +34,12 @@ class HomeController extends Controller
|
||||
public function records(Request $request): Response
|
||||
{
|
||||
$urlParams = $request->all();
|
||||
$users = $this->accountService->all();
|
||||
|
||||
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
|
||||
$filter = data_get($urlParams, "filter") ?? [];
|
||||
$arguments = array_merge([
|
||||
"schema_in" => $this->accountService->currentReadableSchemas(),
|
||||
"status_in" => ["draft", "published"]
|
||||
], $filter);
|
||||
|
||||
@@ -53,8 +53,6 @@ class HomeController extends Controller
|
||||
->sort($sort)
|
||||
->run();
|
||||
|
||||
$users = $this->accountService->all();
|
||||
|
||||
return ok([
|
||||
"users" => $users,
|
||||
"records" => $graph->getRootRecords()->toArray(),
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Lucent\Account\AccountService;
|
||||
use Lucent\Account\AuthService;
|
||||
use Lucent\Account\Role;
|
||||
use Lucent\LucentException;
|
||||
use Lucent\Svelte\Svelte;
|
||||
use function Lucent\Response\fail;
|
||||
@@ -31,19 +30,18 @@ class MemberController extends Controller
|
||||
title: "Members",
|
||||
data: [
|
||||
"users" => $this->accountService->allProfiles()->toArray(),
|
||||
"roles" => Role::cases()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function invite(Request $request)
|
||||
{
|
||||
if (empty($request->input("role"))) {
|
||||
if (empty($request->input("roles"))) {
|
||||
return fail("Select a role for the user");
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->authService->invite($request->input("name"), $request->input("email"), $request->input("role"));
|
||||
$user = $this->authService->invite($request->input("name"), $request->input("email"), $request->input("roles"));
|
||||
} catch (LucentException $th) {
|
||||
return fail($th);
|
||||
}
|
||||
@@ -56,7 +54,7 @@ class MemberController extends Controller
|
||||
public function update(Request $request)
|
||||
{
|
||||
try {
|
||||
$this->authService->changeRole($request->input("id"), $request->input("role"));
|
||||
$this->authService->changeRoles($request->input("id"), $request->input("roles"));
|
||||
} catch (LucentException $th) {
|
||||
return fail($th);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Lucent\Account\AccountService;
|
||||
use Lucent\Account\AuthService;
|
||||
use Lucent\Account\UserRepo;
|
||||
use Lucent\Channel\ChannelService;
|
||||
use Lucent\LucentException;
|
||||
use Lucent\Query\Operator;
|
||||
@@ -28,7 +27,6 @@ class RecordController extends Controller
|
||||
private readonly AuthService $authService,
|
||||
private readonly ChannelService $channelService,
|
||||
private readonly Svelte $svelte,
|
||||
private readonly UserRepo $userRepo,
|
||||
private readonly Query $query,
|
||||
private readonly Manager $recordManager
|
||||
)
|
||||
@@ -38,11 +36,21 @@ class RecordController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$schemaName = $request->route("schemaName");
|
||||
|
||||
if(!in_array($schemaName,$this->accountService->currentReadableSchemas())){
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
view: "recordNotFound",
|
||||
title: "Schema Not Found",
|
||||
);
|
||||
}
|
||||
|
||||
$users = $this->accountService->all();
|
||||
$schema = $this->channelService->getSchema($schemaName)->get();
|
||||
$urlParams = $request->all();
|
||||
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
|
||||
$filter = data_get($urlParams, "filter") ?? [];
|
||||
|
||||
$arguments = array_merge([
|
||||
"schema" => $schema->name,
|
||||
"status_in" => "draft,published",
|
||||
@@ -82,6 +90,7 @@ class RecordController extends Controller
|
||||
"total" => $graph->total ?? 0,
|
||||
"filter" => $request->input("filter") ?? [],
|
||||
"inModal" => true,
|
||||
"isWritable" => in_array($schemaName,$this->accountService->currentWritableSchemas())
|
||||
];
|
||||
|
||||
if ($request->ajax()) {
|
||||
@@ -142,6 +151,14 @@ class RecordController extends Controller
|
||||
|
||||
public function new(Request $request)
|
||||
{
|
||||
if(!in_array($request->input("schema"),$this->accountService->currentWritableSchemas())){
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
view: "recordNotFound",
|
||||
title: "Schema Not Found",
|
||||
);
|
||||
}
|
||||
|
||||
$schema = $this->channelService->channel->schemas->where("name", $request->input("schema"))->first();
|
||||
$recordHistory = $this->recordManager->fromSession($request->session())->getRecords();
|
||||
$record = $this->recordService->createEmpty($schema, $this->authService->currentUserId());
|
||||
@@ -162,6 +179,14 @@ class RecordController extends Controller
|
||||
|
||||
public function newInline(Request $request)
|
||||
{
|
||||
if(!in_array($request->input("schema"),$this->accountService->currentWritableSchemas())){
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
view: "recordNotFound",
|
||||
title: "Schema Not Found",
|
||||
);
|
||||
}
|
||||
|
||||
$schema = $this->channelService->getSchema($request->input("schema"))->get();
|
||||
$record = $this->recordService->createEmpty($schema);
|
||||
$queryRecord = QueryRecord::fromRecord($record);
|
||||
@@ -198,9 +223,17 @@ class RecordController extends Controller
|
||||
}
|
||||
|
||||
$record = $graph->records->first();
|
||||
|
||||
if(!in_array($record->schema,$this->accountService->currentReadableSchemas())){
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
view: "recordNotFound",
|
||||
title: "Schema Not Found",
|
||||
);
|
||||
}
|
||||
|
||||
$schema = $this->channelService->getSchema($record->schema)->get();
|
||||
$recordHistory = $this->recordManager->fromSession($request->session())->push($rid)->getRecords($rid);
|
||||
$users = $this->userRepo->all();
|
||||
return $this->svelte->render(
|
||||
layout: "channel",
|
||||
view: "recordEdit",
|
||||
@@ -209,33 +242,42 @@ class RecordController extends Controller
|
||||
"schema" => $schema,
|
||||
"graph" => toArray($graph),
|
||||
"record" => toArray($record),
|
||||
"users" => $users,
|
||||
"users" => $this->accountService->all(),
|
||||
"recordHistory" => $recordHistory,
|
||||
"isWritable" => in_array($record->schema,$this->accountService->currentWritableSchemas())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function editInline(Request $request)
|
||||
{
|
||||
$rid = $request->route("rid");
|
||||
|
||||
$graph = $this->query
|
||||
->filter(["id" => $rid])
|
||||
->limit(1)
|
||||
->childrenDepth(2)
|
||||
->parentsDepth(1)
|
||||
->run();
|
||||
|
||||
$record = $graph->records->first();
|
||||
|
||||
return ok(
|
||||
[
|
||||
"graph" => toArray($graph),
|
||||
"record" => toArray($record)
|
||||
]
|
||||
);
|
||||
}
|
||||
// public function editInline(Request $request)
|
||||
// {
|
||||
// $rid = $request->route("rid");
|
||||
//
|
||||
// $graph = $this->query
|
||||
// ->filter(["id" => $rid])
|
||||
// ->limit(1)
|
||||
// ->childrenDepth(2)
|
||||
// ->parentsDepth(1)
|
||||
// ->run();
|
||||
//
|
||||
// $record = $graph->records->first();
|
||||
//
|
||||
// if(!in_array($record->schema,$this->accountService->currentReadableSchemas())){
|
||||
// return $this->svelte->render(
|
||||
// layout: "channel",
|
||||
// view: "recordNotFound",
|
||||
// title: "Schema Not Found",
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// return ok(
|
||||
// [
|
||||
// "graph" => toArray($graph),
|
||||
// "record" => toArray($record)
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
public function suggestions(Request $request)
|
||||
|
||||
@@ -4,22 +4,27 @@ namespace Lucent\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Lucent\Account\AccountService;
|
||||
use Lucent\Account\AuthService;
|
||||
use Lucent\Channel\ChannelService;
|
||||
|
||||
readonly class AuthMiddleware
|
||||
{
|
||||
public function __construct(private AuthService $authService, private ChannelService $channelService)
|
||||
public function __construct(
|
||||
private AccountService $accountService,
|
||||
private AuthService $authService,
|
||||
private ChannelService $channelService
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
return redirect($this->channelService->channel->lucentUrl . "/login");
|
||||
}
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ Route::group([
|
||||
Route::get('/suggestions', [RecordController::class, 'suggestions']);
|
||||
Route::get('/{rid}', [RecordController::class, 'edit']);
|
||||
Route::post('/clone/{rid}', [RecordController::class, 'clone']);
|
||||
Route::get('/editInline/{rid}', [RecordController::class, 'editInline']);
|
||||
// Route::get('/editInline/{rid}', [RecordController::class, 'editInline']);
|
||||
Route::get('/{rid}/parents', [RecordController::class, 'parents']);
|
||||
Route::post('/', [RecordController::class, 'save']);
|
||||
Route::post('/status/{status}', [RecordController::class, 'status']);
|
||||
|
||||
@@ -22,6 +22,8 @@ class CollectionSchema implements Schema
|
||||
public string $color = "",
|
||||
public string $titleTemplate = "",
|
||||
public int $revisions = 0,
|
||||
public array $read = [],
|
||||
public array $write = [],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ class FilesSchema implements Schema
|
||||
public string $color = "",
|
||||
public string $titleTemplate = "",
|
||||
public int $revisions = 0,
|
||||
public array $read = [],
|
||||
public array $write = [],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class SchemaService
|
||||
color: $schemaArr["color"] ?? "",
|
||||
titleTemplate: $schemaArr["titleTemplate"] ?? "",
|
||||
revisions: $schemaArr["revisions"] ?? 0,
|
||||
read: $schemaArr["read"] ?? [],
|
||||
write: $schemaArr["write"] ?? [],
|
||||
),
|
||||
"files" => new FilesSchema(
|
||||
name: $schemaArr["name"],
|
||||
@@ -37,6 +39,8 @@ class SchemaService
|
||||
color: $schemaArr["color"] ?? "",
|
||||
titleTemplate: $schemaArr["titleTemplate"] ?? "",
|
||||
revisions: $schemaArr["revisions"] ?? 0,
|
||||
read: $schemaArr["read"] ?? [],
|
||||
write: $schemaArr["write"] ?? [],
|
||||
),
|
||||
"block" => new BlockSchema(
|
||||
name: $schemaArr["name"],
|
||||
@@ -62,6 +66,8 @@ class SchemaService
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// /**
|
||||
// * @param array<string> $visible
|
||||
|
||||
@@ -4,12 +4,14 @@ namespace Lucent\Svelte;
|
||||
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Lucent\Account\AccountService;
|
||||
use Lucent\Channel\ChannelService;
|
||||
|
||||
class Svelte
|
||||
{
|
||||
public function __construct(
|
||||
public ChannelService $channelService
|
||||
public ChannelService $channelService,
|
||||
public AccountService $accountService
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -24,6 +26,7 @@ class Svelte
|
||||
$context["title"] = $title;
|
||||
$context["data"] = $data;
|
||||
$context["channel"] = $this->channelService->channel;
|
||||
$context["readableSchemas"] = $this->accountService->currentReadableSchemas();
|
||||
$json = json_encode($context);
|
||||
|
||||
$divTag = sprintf('<div class="lucent-component" data-layout="%s"></div>', $layout);
|
||||
|
||||
Reference in New Issue
Block a user