permissions

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