edit delete schemas

This commit is contained in:
2026-01-08 18:13:17 +02:00
parent 25ad3fefab
commit 9dcc6b4e12
21 changed files with 317 additions and 107 deletions
@@ -0,0 +1,53 @@
<script>
import AccountLayout from "../../layouts/AccountLayout.svelte";
import { post } from "../../modules/remote";
let { channel } = $props();
let email = $state("");
let message = $state("");
let isLoading = $state(false);
$inspect(channel);
function login(e) {
e.preventDefault();
isLoading = true;
post(
channel.lucentUrl + "/login",
{
email: email,
},
(data, err) => {
isLoading = false;
message = "You will receive an email with a login link";
},
);
}
</script>
<AccountLayout {body} {channel}></AccountLayout>
{#snippet body()}
<div class="wrapper-tiny">
{#if message}
<div class="alert alert-info" role="alert">
{message}
</div>
{:else}
<form onsubmit={login}>
<div class="mb-3">
<label for="emailaddress" class="form-label"
>Email address</label
>
<input
type="email"
bind:value={email}
class="form-control"
id="emailaddress"
required
/>
</div>
<div class="text-center mt-5 d-block">
<button aria-busy={isLoading}>Login</button>
</div>
</form>
{/if}
</div>
{/snippet}
@@ -1,6 +1,7 @@
<script>
import ChannelLayout from "../../layouts/ChannelLayout.svelte";
import Sortable from "../../common/Sortable.svelte";
import DeleteButton from "../../common/DeleteButton.svelte";
import { post } from "../../modules/remote";
import { getApp } from "../../app";
let { channel, user, data } = $props();
@@ -8,18 +9,36 @@
function handleSchemaCreate(e) {
e.preventDefault();
// post(
// channel.lucentUrl + "/schemas",
// {
// name: newSchemaName,
// alias: newSchemaAlias,
// },
// (data, err) => {
// if (err.isEmpty()) {
// Turbo.visit(window.location.href);
// }
// },
// );
post(
channel.lucentUrl + "/schemas/update",
{
id: data.schema.id,
name: data.schema.name,
alias: data.schema.alias,
revisions: data.schema.revisions,
},
(data, err) => {
if (err.isEmpty()) {
Turbo.visit(channel.lucentUrl + "/schemas");
}
},
);
}
function handleDelete() {
post(
app.url("schemas/delete"),
{
schemaId: data.schema.id,
},
(data, err) => {
if (err.isEmpty()) {
Turbo.visit(app.url("schemas"));
} else {
console.log(err);
}
},
);
}
</script>
@@ -54,8 +73,26 @@
Developers will use this to reference the field
</small>
</label>
<label>
Revision number
<input
bind:value={data.schema.revisions}
type="number"
required
aria-describedby="revision-helper"
/>
<small id="revision-helper">
How many revisions per document will be kept
</small>
</label>
</fieldset>
<button type="submit">Update</button>
</form>
<DeleteButton onDelete={handleDelete}>
{#snippet text()}
Delete schema
{/snippet}
</DeleteButton>
{/snippet}
@@ -0,0 +1,35 @@
<script>
import AccountLayout from "../../layouts/AccountLayout.svelte";
import { post } from "../../modules/remote";
let { channel, data } = $props();
let isLoading = $state(false);
function login(e) {
e.preventDefault();
isLoading = true;
post(
channel.lucentUrl + "/verify",
{
email: data.email,
token: data.token,
},
(data, err) => {
window.location = channel.lucentUrl;
},
);
}
</script>
<AccountLayout {body} {channel}></AccountLayout>
{#snippet body()}
<div class="wrapper-tiny">
<form onsubmit={login}>
<div class="mb-3 text-center">
<h3>Login as {data.email}</h3>
</div>
<div class="text-center mt-5 d-block">
<button aria-busy={isLoading}>Enter</button>
</div>
</form>
</div>
{/snippet}
+16
View File
@@ -0,0 +1,16 @@
<script>
let { body, channel, user } = $props();
</script>
<div
style="text-align: center;background: var(--p20);padding: 20px;color: var(--p90)"
>
<h1>
<a class="text-decoration-none" href={channel.url}
>{channel.name ?? "Lucent Setup"}</a
>
</h1>
</div>
<div>
{@render body()}
</div>
+4 -4
View File
@@ -3,8 +3,8 @@ import * as Turbo from "@hotwired/turbo";
import { mount, unmount } from "svelte";
import "../css/app.css";
import Register from "./svelte/account/Register.svelte";
import Login from "./svelte/account/Login.svelte";
import Verify from "./svelte/account/Verify.svelte";
import LoginEntry from "./entry/LoginEntry/LoginEntry.svelte";
import VerifyEntry from "./entry/VerifyEntry/VerifyEntry.svelte";
import Profile from "./svelte/account/Profile.svelte";
import SetupIndex from "./svelte/setup/Index.svelte";
import Members from "./svelte/members/Members.svelte";
@@ -27,8 +27,8 @@ const entryComponents = {
homeIndex: HomeEntry,
buildReport: BuildReport,
register: Register,
login: Login,
verify: Verify,
login: LoginEntry,
verify: VerifyEntry,
profile: Profile,
setup: SetupIndex,
schemas: SchemaEntry,
+12 -15
View File
@@ -3,7 +3,7 @@
import RecordNotFound from "./records/NotFound.svelte";
import RecordEdit from "./records/Edit.svelte";
import ContentIndex from "./content/Index.svelte";
import {setContext} from "svelte";
import { setContext } from "svelte";
import Navbar from "./layout/Navbar.svelte";
import HomeIndex from "./home/Index.svelte";
import BuildReport from "./build/Report.svelte";
@@ -28,24 +28,21 @@
export let axios;
export let readableSchemas;
setContext("axios", axios);
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);
</script>
<div class="main-wrapper">
<div class="sidebar-content">
<Navbar schema={data.schema}/>
<div class="sidebar-content">
<Navbar schema={data.schema} />
</div>
<div class="main-content">
<Header />
<svelte:component this={components[view]} {title} {...data} />
</div>
</div>
<div class="main-content">
<Header />
<svelte:component this={components[view]} {title} {...data}/>
</div>
</div>
+7
View File
@@ -1128,6 +1128,7 @@
"integrity": "sha512-a+uxqQ9j6Lxmq4plbGaNdM9hgDCZyxAv/yvuyF5iWoA2H5icZkqD3rdK155ZQgFLX2lc3NvahHG4OgKpYqYPiQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"deepmerge": "^4.3.1",
@@ -1174,6 +1175,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4322,6 +4324,7 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
"integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
"dev": true,
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -4362,6 +4365,7 @@
"integrity": "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -4424,6 +4428,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -4461,6 +4466,7 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -4554,6 +4560,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
+22
View File
@@ -0,0 +1,22 @@
<?php namespace Lucent\Core\Auth;
use Illuminate\Support\Facades\Session;
class AuthModule
{
public static function getCurrentUserId(): ?string
{
if (app()->runningInConsole()) {
return config("lucent.systemUserId");
} elseif (request()->segment(1) !== "lucent") {
return config("lucent.systemUserId");
} else {
return Session::get("user.id");
}
}
public static function isLoggedIn(): bool
{
return !empty(static::getCurrentUserId());
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php namespace Lucent\Core\Channel;
use Lucent\Core\Data\Channel;
class ChannelModule
{
public static function get(): Channel
{
$appUrl = rtrim(config("lucent.url") ?? "", "/");
return new Channel(url: $appUrl, lucentUrl: $appUrl . "/lucent");
}
}
+6
View File
@@ -0,0 +1,6 @@
<?php namespace Lucent\Core\Data;
class Channel
{
public function __construct(public string $url, public string $lucentUrl) {}
}
+5
View File
@@ -46,4 +46,9 @@ class FieldRepo
{
DB::table(self::TABLE_NAME)->where("id", $fieldId)->delete();
}
public static function deleteBySchemaId(string $schemaId): void
{
DB::table(self::TABLE_NAME)->where("schema_id", $schemaId)->delete();
}
}
+12
View File
@@ -33,4 +33,16 @@ class SchemaRepo
{
DB::table(self::TABLE_NAME)->insert(SchemaModule::toDb($schema));
}
public static function update(Schema $schema): void
{
DB::table(self::TABLE_NAME)
->where("id", $schema->id)
->update(SchemaModule::toDb($schema));
}
public static function delete(string $schemaId): void
{
DB::table(self::TABLE_NAME)->where("id", $schemaId)->delete();
}
}
-2
View File
@@ -7,13 +7,11 @@ class Schema
* @param string $alias
* @param string $name
* @param int $revisions
* @param Field[] $fields
*/
public function __construct(
public string $id,
public string $alias,
public string $name,
public int $revisions,
public array $fields,
) {}
}
-2
View File
@@ -12,7 +12,6 @@ class SchemaModule
alias: $data["alias"],
name: $data["label"],
revisions: $data["revisions"],
fields: array_map(FieldModule::fromArray(...), $data["fields"]),
);
}
@@ -23,7 +22,6 @@ class SchemaModule
alias: data_get($data, "alias"),
name: data_get($data, "name"),
revisions: data_get($data, "revisions"),
fields: [],
);
}
+16 -22
View File
@@ -10,6 +10,7 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
use Lucent\Core\Data\Channel;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Svelte\Svelte;
@@ -18,39 +19,33 @@ use Lucent\Util\Form\ResponseFormError;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
class AuthController
{
public function __construct(
private readonly AuthService $authService,
private readonly AuthService $authService,
private readonly AccountService $accountService,
private readonly ChannelService $channelService,
private readonly Session $session,
private readonly Svelte $svelte,
)
{
}
private readonly Session $session,
private readonly Svelte $svelte,
) {}
public function register(Request $request): View|RedirectResponse
{
if ($this->accountService->countUsers() > 0) {
return redirect($this->channelService->channel->lucentUrl . "/login");
return redirect(
$this->channelService->channel->lucentUrl . "/login",
);
}
return $this->svelte->render(
layout: "account",
view: "register",
title: "Create an account",
);
}
public function postRegister(Request $request): Response
{
if ($this->accountService->countUsers() > 0) {
abort(400);
}
@@ -70,44 +65,43 @@ class AuthController
public function login()
{
if ($this->accountService->countUsers() == 0) {
return redirect($this->channelService->channel->lucentUrl . "/register");
return redirect(Channel::get()->url . "/register");
}
return view("lucent::auth.login");
return Svelte::view("login", "Login", []);
}
public function postLogin(Request $request)
{
$this->authService->sendLoginEmail($request->input("email"));
return view("lucent::auth.login-success");
return [];
}
public function verify(Request $request): View
{
return view("lucent::auth.verify", [
return Svelte::view("verify", "Verify", [
"email" => $request->input("email"),
"token" => $request->input("token"),
]);
}
public function postVerify(Request $request)
{
try {
$this->authService->login($request->input("email"), $request->input("token"));
$this->authService->login(
$request->input("email"),
$request->input("token"),
);
} catch (LucentException $th) {
return ResponseFormError::fromException($th);
}
return [];
}
public function logout(): RedirectResponse
{
$this->session->flush();
return redirect($this->channelService->channel->lucentUrl . "/login");
}
}
+3 -7
View File
@@ -2,20 +2,16 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
use Lucent\Core\Repository\FieldRepo;
use Lucent\Core\Repository\SchemaRepo;
use Lucent\Core\Schema\Data\Field;
use Lucent\Core\Schema\Data\FieldProp\FieldProp;
use Lucent\Core\Schema\Data\Schema;
use Lucent\Id\Id;
use Lucent\Svelte\Svelte;
use Illuminate\Support\Facades\Validator;
class FieldController extends Controller
class FieldController
{
public function __construct(private readonly Svelte $svelte) {}
@@ -36,7 +32,7 @@ class FieldController extends Controller
// $fieldProps = FieldProp::fromType($fieldType);
return $this->svelte->render(
return Svelte::view(
view: "fieldCreate",
title: "Create Field",
data: [
@@ -99,7 +95,7 @@ class FieldController extends Controller
$schemas = SchemaRepo::all();
$schema = collect($schemas)->firstWhere("id", $field->schemaId);
return $this->svelte->render(
return Svelte::view(
view: "fieldEdit",
title: "Edit Field",
data: [
+2 -3
View File
@@ -14,14 +14,13 @@ use function Lucent\Response\ok;
class HomeController extends Controller
{
public function __construct(
private readonly Svelte $svelte,
private readonly AccountService $accountService,
private readonly Query $query,
) {}
public function home(): View
public function home()
{
return $this->svelte->render(view: "homeIndex", title: "Records");
return Svelte::view("homeIndex", "Records", []);
}
public function records(Request $request): Response
+47 -13
View File
@@ -2,10 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
use Lucent\Core\Repository\SchemaRepo;
use Lucent\Core\Repository\FieldRepo;
use Lucent\Core\Schema\Data\Schema;
@@ -13,20 +10,14 @@ use Lucent\Id\Id;
use Lucent\Svelte\Svelte;
use Illuminate\Support\Facades\Validator;
class SchemaController extends Controller
class SchemaController
{
public function __construct(
private readonly AuthService $authService,
private readonly AccountService $accountService,
private readonly Svelte $svelte,
) {}
public function home()
{
$schemas = SchemaRepo::all();
$fields = FieldRepo::all();
return $this->svelte->render(
return Svelte::view(
view: "schemas",
title: "Schemas",
data: [
@@ -51,7 +42,6 @@ class SchemaController extends Controller
alias: $request->input("alias"),
name: $request->input("name"),
revisions: 0,
fields: [],
);
SchemaRepo::insert($schema);
@@ -71,7 +61,7 @@ class SchemaController extends Controller
return response()->json(["errors" => ["Schema not found"]], 404);
}
return $this->svelte->render(
return Svelte::view(
view: "schemaEdit",
title: "Edit Schema",
data: [
@@ -79,4 +69,48 @@ class SchemaController extends Controller
],
);
}
public function postUpdate(Request $request)
{
$validator = Validator::make($request->all(), [
"name" => "required|string|max:30|min:2|",
"alias" => "required|alpha_dash:ascii|max:30|min:2|",
]);
if ($validator->fails()) {
return response()->json(["errors" => $validator->errors()], 422);
}
$schema = SchemaRepo::findOne($request->input("id"));
if (empty($schema)) {
return response()->json(["errors" => ["Schema not found"]], 404);
}
$schema = new Schema(
id: $schema->id,
alias: $request->input("alias"),
name: $request->input("name"),
revisions: $request->input("revisions"),
);
SchemaRepo::update($schema);
return response()->json(
["message" => "Schema created successfully"],
201,
);
}
public function postDelete(Request $request)
{
$schemaId = $request->input("schemaId");
$schema = SchemaRepo::findOne($schemaId);
if (empty($schema)) {
return response()->json(["errors" => ["Schema not found"]], 404);
}
SchemaRepo::delete($schemaId);
FieldRepo::deleteBySchemaId($schemaId);
return response()->json([], 200);
}
}
+4 -6
View File
@@ -4,18 +4,16 @@ namespace Lucent\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Lucent\Account\AuthService;
use Lucent\Core\Auth\AuthModule;
readonly class GuestMiddleware
{
public function __construct(private AuthService $authService)
{
}
public function __construct() {}
public function handle(Request $request, Closure $next)
{
if ($this->authService->isLoggedIn()) {
return redirect("/lucent");
if (AuthModule::isLoggedIn()) {
return redirect(config("lucent.url"));
}
return $next($request);
}
+8
View File
@@ -63,6 +63,14 @@ Route::group(
Route::get("/schemas", [SchemaController::class, "home"]);
Route::get("/schemas/edit/{id}", [SchemaController::class, "edit"]);
Route::post("/schemas", [SchemaController::class, "postCreate"]);
Route::post("/schemas/update", [
SchemaController::class,
"postUpdate",
]);
Route::post("/schemas/delete", [
SchemaController::class,
"postDelete",
]);
Route::get("/fields/create", [FieldController::class, "create"]);
Route::get("/fields/edit/{id}", [FieldController::class, "edit"]);
Route::post("/fields", [FieldController::class, "postCreate"]);
+4 -21
View File
@@ -2,32 +2,18 @@
namespace Lucent\Svelte;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Lucent\Account\AccountService;
use Lucent\Channel\ChannelService;
use Lucent\Core\Channel\ChannelModule;
class Svelte
{
public function __construct(
public ChannelService $channelService,
public AccountService $accountService,
) {}
function render(
string $view,
string $title = "",
mixed $data = [],
): View|Factory {
public static function view(string $view, string $title, mixed $data)
{
$context = [];
$context["user"] = session("user");
$context["view"] = $view;
$context["title"] = $title;
$context["data"] = $data;
$context["channel"] = $this->channelService->channel;
$context[
"readableSchemas"
] = $this->accountService->currentReadableSchemas();
$context["channel"] = ChannelModule::get();
$json = json_encode($context);
$divTag = '<div class="lucent-component"></div>';
@@ -40,10 +26,7 @@ class Svelte
return view("lucent::svelte", [
"svelte" => $svelte,
"view" => $view,
"data" => $data,
"title" => $title,
"channel" => $this->channelService->channel,
]);
}
}