Compare commits

...

15 Commits

Author SHA1 Message Date
lexx ab1517cc8f tabs and date in modal fix 2024-08-30 13:38:34 +03:00
lexx 9f724a3243 better report 2024-08-27 17:59:12 +03:00
lexx ae65ca47f6 commands and logs to the database 2024-08-27 17:42:06 +03:00
lexx 74d2fcc4fa build assets 2024-08-27 12:25:42 +03:00
lexx 82174afdea fixing multiple references 2024-08-27 12:24:51 +03:00
lexx ffc39f078d trix 2024-08-25 14:45:49 +03:00
lexx 7c4e19afbc tip tap and trix 2024-08-25 14:23:20 +03:00
lexx 7b10bfca1d cleanup 2024-08-24 19:57:17 +03:00
lexx 0e5ac08641 actions 2024-08-24 19:35:07 +03:00
lexx 1505aaa909 multiple commands 2024-08-24 18:51:36 +03:00
lexx d9e2c4954a refactoring of filters 2024-08-24 17:22:40 +03:00
lexx 97ad9de3d2 readme 2024-08-24 01:30:44 +03:00
lexx 9e140be0ec updated readme 2024-08-23 21:06:53 +03:00
lexx a737c2d571 configurable disks 2024-08-23 20:58:45 +03:00
lexx c43c29eb14 modal save button 2024-08-23 19:37:20 +03:00
117 changed files with 4960 additions and 1687 deletions
+25 -3
View File
@@ -9,8 +9,8 @@ include_toc: true
### Requirements
- PHP 8.2
- Laravel 10
- PHP 8.3
- Laravel 11
- Postgres or Sqlite database
- ImageMagick
@@ -82,7 +82,9 @@ return [
### Database
The recommended database for small website is sqlite. But you can also use postresql
Make sure to delete the existing migration scripts in your database/migrations folder.
> [!CAUTION]
> Make sure to delete the existing migration scripts in your database/migrations folder.
Then run:
@@ -90,6 +92,26 @@ Then run:
php artisan migrate
```
### File Storage
You can use your local filesystem or s3 compatible storage. Lucent expects you to have a valid configuration inside ``config/filesystems.php``
example:
```php
return [
'disks' => [
'lucent' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => true,
],
],
];
```
### First user
To create your first user, head to your localhost:8000/lucent
+32
View File
@@ -0,0 +1,32 @@
# Upgrade from 1.1.* to 1.2.0
## lucent.php config file
There is now an array of commands, accepting more than one.
from
```php
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
```
to
```php
"commands" => [
"generate:static" => "Build Website",
],
```
## config/filesystems.php
Lucent has its own filesystem.
You should now add:
```
'lucent' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
```
+3 -3
View File
@@ -8,14 +8,14 @@
"ext-zip": "*",
"ext-sqlite3": "*",
"ext-imagick": "*",
"ext-pdo": "*",
"php": "^8.3",
"guzzlehttp/guzzle": "^7.2",
"intervention/image": "^2.7",
"phpoption/phpoption": "^1.9",
"spatie/image-optimizer": "^1.6",
"staudenmeir/laravel-cte": "^1.0",
"ext-pdo": "*",
"mustache/mustache": "^2.14"
"staudenmeir/laravel-cte": "^1.0"
},
"require-dev": {
"phpstan/phpstan": "^1.8",
+5 -11
View File
@@ -24,7 +24,8 @@ There are 3 types of schemas
- **fields**: The list of your fields. Look the field reference for more
- **isEntry**: If this schema is important, it will show be visible on the main the sidebar. Default: false _optional_
- **sortBy**: The default sorting in the content browser _optional_
- **titleTemplate**: Mustache code to customize the preview field _optional_
- **cardTitle**: Mustache code to customize the preview field _optional_
- **cardImage**: Field name of image you want to use as a preview image _optional_
- **revisions**: How many revisions are going to be kept for each record _optional_
- **read**: Array of user groups that have read permissions _optional_
- **write**: Array of user groups that have write permissions _optional_
@@ -40,20 +41,12 @@ There are 3 types of schemas
- **fields**: The list of your fields. Look the field reference for more
- **isEntry**: If this schema is important, it will show be visible on the main the sidebar _optional_
- **sortBy**: The default sorting in the content browser _optional_
- **titleTemplate**: Mustache code to customize the preview field _optional_
- **cardTitle**: Mustache code to customize the preview field _optional_
- **revisions**: How many revisions are going to be kept for each record _optional_
- **read**: Array of user groups that have read permissions _optional_
- **write**: Array of user groups that have write permissions _optional_
## Block Reference
- **name**: The ID of the collection. Camelcase and plural is the recommended format ex. blogPosts
- **label**: The friendly name of the schema
- **type**: The type of the collection. Should be "block"
- **fields**: The list of your fields. Look the field reference for more
A full Collection example without the fields:
```json
@@ -74,7 +67,8 @@ A full Collection example without the fields:
"SEO"
],
"sortBy": "-_sys.createdAt",
"titleTemplate": "{{name}} {{slug}}",
"schemaTitle": "{{name}} {{slug}}",
"schemaImage": "cover",
"revisions": 15,
"read": [
"admin",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,11 +1,11 @@
{
"main.js": {
"file": "assets/main-0h36XLim.js",
"file": "assets/main-D9joEh0I.js",
"name": "main",
"src": "main.js",
"isEntry": true,
"css": [
"assets/main-CaexgiEy.css"
"assets/main-BnJW41Dx.css"
]
}
}
+22 -10
View File
@@ -1,22 +1,25 @@
<script>
import {getContext, onMount} from "svelte";
import axios from "axios";
const channel = getContext("channel");
export let title;
export let command;
$: date = "";
$: logs = "";
let anchorEl;
let inProgress = false;
function connect() {
const eventSource = new EventSource(channel.lucentUrl + "/build-report-source");
const eventSource = new EventSource(channel.lucentUrl + "/command-report-source/" + command.signature );
eventSource.onmessage = function (event) {
inProgress = true;
const data = JSON.parse(event.data);
date = data.date;
logs = data.logs;
anchorEl.scrollIntoView()
}
eventSource.onerror = (e) => {
console.log(e)
@@ -28,8 +31,7 @@
function buildWebsite(e) {
e.preventDefault();
inProgress = true;
axios.post(channel.lucentUrl + "/build").then(response => {
axios.post(channel.lucentUrl + "/command/" + command.signature).then(response => {
connect()
})
@@ -46,26 +48,36 @@
<h3 class="header-small mb-5">{title}</h3>
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start Build
<button on:click={buildWebsite} class="button primary mb-3" disabled={inProgress}>Start
</button>
<div class="mb-3">
{#if inProgress}
<span class="badge text-bg-warning">
Build in progress
Action in progress
</span>
{/if}
{#if !inProgress && logs}
<span class="badge text-bg-info">
Build completed
Action completed
</span>
{/if}
</div>
<pre>{logs}</pre>
<pre class="logs">{logs}
<div bind:this={anchorEl}>&nbsp;</div>
</pre>
</div>
</div>
<style>
.logs{
max-height: 70vh;
overflow: scroll;
background: var(--p90);
color: var(--p10);
padding: 10px;
}
</style>
+13
View File
@@ -113,8 +113,21 @@
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12l4-4m-4 4 4 4"/>',
viewBox: "0 0 24 24",
},
"list": {
path: '<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M9 8h10M9 12h10M9 16h10M4.99 8H5m-.02 4h.01m0 4H5"/>',
viewBox: "0 0 24 24",
},
"ordered-list": {
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6h8m-8 6h8m-8 6h8M4 16a2 2 0 1 1 3.321 1.5L4 20h5M4 5l2-1v6m-2 0h4"/>',
viewBox: "0 0 24 24",
},
"italic": {
path: '<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m8.874 19 6.143-14M6 19h6.33m-.66-14H18"/>',
viewBox: "0 0 24 24",
}
};
export let width = 16;
export let height = 16;
export let icon = "";
@@ -12,7 +12,6 @@
export let inModal;
export let modalUrl;
export let graph;
let filter = {
label: "",
operator: "",
@@ -58,6 +57,7 @@
const filterRecord = extractFilterRecord(graph, value);
function extractFilterRecord(graph, value) {
if (!filter.isReference) {
return null;
}
@@ -82,7 +82,7 @@
{#if filter.isReference && filterRecord}
{filter.label} is {previewTitle(channel.schemas, filterRecord)}
{:else}
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {value}
{filter.label} {operators.find((o) => o.name === filter.operator)?.symbol ?? ""} {operators.find((o) => o.name === filter.operator)?.hasValue ? value : ""}
{/if}
<button
@@ -11,7 +11,6 @@
export let inModal;
export let modalUrl;
let dropdown;
let search = "";
let systemFieldsFiltered = systemFields;
@@ -70,6 +69,13 @@
activeOperator = operators.find(o => o.name === "eq")
}
function selectOperator(e, operator) {
activeOperator = operator;
if (!operator.hasValue) {
applyFilter(e)
}
}
function applyFilter(e) {
e.preventDefault();
let filterPrefix = "";
@@ -146,7 +152,7 @@
<div class="selected-filter">field: {activeField.label}</div>
{#each activeOperators as operator}
<button class="dropdown-item button" on:click={e => activeOperator = operator }>
<button class="dropdown-item button" on:click={e => selectOperator(e,operator)}>
{operator.label}
</button>
{/each}
@@ -214,8 +220,8 @@
required
/>
<button class="button applied-filter">
Submit
<button class="button applied-filter">
Submit
</button>
</form>
+13 -5
View File
@@ -1,17 +1,25 @@
<script>
import Avatar from "../account/Avatar.svelte";
import {getContext} from "svelte";
import Dropdown from "../common/Dropdown.svelte";
const channel = getContext("channel");
const user = getContext("user");
console.log( channel.commands)
</script>
<div class="top-nav ">
<a class="top-nav-item" href="{channel.lucentUrl}/members">Members</a>
{#if channel.generateCommand}
<a href="{channel.lucentUrl}/build-report" class="top-nav-item">Build website</a>
{#if channel.commands.length > 0}
<Dropdown>
<div slot="button">Actions</div>
{#each channel.commands as command}
<a href="{channel.lucentUrl}/command-report/{command.signature}" class="top-nav-item">{command.name}</a>
{/each}
</Dropdown>
{/if}
<!-- <div>-->
<!-- <form method="GET">-->
@@ -19,8 +27,8 @@
<!-- class="form-control" required/>-->
<!-- </form>-->
<!-- </div>-->
<a href="{channel.lucentUrl}/profile">
<Avatar side="28" name={user.name}/>
</a>
<a href="{channel.lucentUrl}/profile">
<Avatar side="28" name={user.name}/>
</a>
</div>
+2 -2
View File
@@ -100,8 +100,8 @@
tinymce.init({...config, ...additionalConfig});
});
export function insertMedia(html){
activeEditor.execCommand('InsertHTML', false, html);
export function insertMedia(info){
activeEditor.execCommand('InsertHTML', false, info.html);
}
</script>
+104 -26
View File
@@ -3,9 +3,12 @@
import {Editor} from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Dropcursor from '@tiptap/extension-dropcursor'
import Text from '@tiptap/extension-text'
import Heading from '@tiptap/extension-heading'
import HardBreak from '@tiptap/extension-hard-break'
import Blockquote from '@tiptap/extension-blockquote';
import CodeBlock from '@tiptap/extension-code-block';
import Bold from '@tiptap/extension-bold';
import BulletList from '@tiptap/extension-bullet-list';
import Code from '@tiptap/extension-code';
@@ -19,6 +22,8 @@
import TableCell from '@tiptap/extension-table-cell';
import TableHeader from '@tiptap/extension-table-header';
import Underline from '@tiptap/extension-underline';
import Image from '@tiptap/extension-image';
import Icon from "../common/Icon.svelte";
let element;
let editor;
@@ -32,19 +37,22 @@
Paragraph,
Text,
Bold,
ListItem,
BulletList,
Code,
CodeBlock,
History,
Italic,
ListItem,
HardBreak,
OrderedList,
ListItem,
Strike,
Table,
TableRow,
TableCell,
TableHeader,
Underline,
Dropcursor,
Image,
Heading.configure({
levels: [1, 2, 3],
}),
@@ -56,7 +64,11 @@
// force re-render so `editor.isActive` works as expected
editor = editor;
},
onUpdate: ({editor}) => {
value = editor.getHTML()
},
});
});
onDestroy(() => {
@@ -64,33 +76,99 @@
editor.destroy();
}
});
export function insertMedia(info){
editor.chain().focus().setImage({ src: info.url }).run()
}
</script>
{#if editor}
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
class:active={editor.isActive('heading', { level: 1 })}
>
H1
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
class:active={editor.isActive('heading', { level: 2 })}
>
H2
</button>
<button
on:click={() => editor.chain().focus().setParagraph().run()}
class:active={editor.isActive('paragraph')}
>
P
</button>
<button
on:click={() => editor.chain().focus().toggleBold().run()}
class:active={editor.isActive('bold')}
>
Bold
</button>
<div class="editor-toolbar">
<button
class="button"
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
class:active={editor.isActive('heading', { level: 1 })}
>
H1
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
class:active={editor.isActive('heading', { level: 2 })}
>
H2
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleBold().run()}
class:active={editor.isActive('bold')}
>
B
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleItalic().run()}
class:active={editor.isActive('italic')}
>
<em>IT</em>
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleUnderline().run()}
class:active={editor.isActive('underline')}
>
<u>U</u>
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleStrike().run()}
class:active={editor.isActive('strike')}
>
<s>S</s>
</button>
<button
class="button"
on:click={() => editor.commands.unsetAllMarks()}
>
Clear
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleCode().run()}
class:active={editor.isActive('code')}
>
Code
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleBulletList().run()}
class:active={editor.isActive('bulletList')}
>
<Icon icon="list"></Icon>
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleOrderedList().run()}
class:active={editor.isActive('orderedList')}
>
<Icon icon="ordered-list"></Icon>
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleBlockquote().run()}
class:active={editor.isActive('blockquote')}
>
""
</button>
<button
class="button"
on:click={() => editor.chain().focus().toggleCodeBlock().run()}
class:active={editor.isActive('codeBlock')}
>
cb
</button>
</div>
{/if}
<div bind:this={element} class="content"/>
+41 -9
View File
@@ -1,21 +1,53 @@
<script>
import {onMount} from "svelte";
import {onDestroy, onMount} from "svelte";
import Trix from "trix"
import customcss from "./tinymce.css?inline";
import "trix/dist/trix.css"
export let value = "";
let textareaEl;
let lastVal;
let editorWrapper;
let activeEditor;
export let field;
let editor;
function updateValue(e) {
value = e.target.value;
}
export function insertMedia(info){
if(info.record._file.width > 0){
var attachment = new Trix.Attachment({ content: info.html })
editor.editor.insertAttachment(attachment)
}else{
editor.editor.insertHTML(`<a href="${info.originalUrl}">${info.record._file.originalName}</a>`)
}
}
onMount(() => {
editor.addEventListener("trix-file-accept", (e) => {
e.preventDefault();
})
})
// onDestroy(() => {
// editor.removeEventListener("trix-before-initialize")
// })
Trix.config.blockAttributes.default.breakOnReturn = false
console.log(Trix.config)
</script>
<div bind:this={editorWrapper} class="tox-wrapper">
<input bind:this={textareaEl} id="x" bind:value type="hidden">
<trix-editor class="trix-content content" input="x"></trix-editor>
<div class="tox-wrapper">
<input id="x-{field.name}" {value} type="hidden">
<trix-editor
bind:this={editor}
class=" content"
input="x-{field.name}"
role="textbox"
tabindex="0"
on:trix-change={updateValue}
></trix-editor>
</div>
@@ -9,13 +9,17 @@
export let value;
export let isCreateMode;
export let validationErrors;
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
$: errorMessage = getErrorMessage(validationErrors, field.name);
export let id;
let wrapperDiv;
let pickerInput;
let pickerInstance;
let flatpickrOptions = {
appendTo: wrapperDiv,
static: true,
allowInput: true,
altInput: true,
altFormat: "Y-m-d H:i:S",
@@ -40,7 +44,7 @@
});
</script>
<div class="mb-0">
<div class="mb-0" bind:this={wrapperDiv}>
<input
type="text"
@@ -43,6 +43,7 @@
function createInlineReference(e, schemaUId) {
e.preventDefault();
inLineCreateRecord = null;
axios
.get(channel.lucentUrl + "/records/newInline?schema=" + schemaUId)
.then((response) => {
@@ -59,27 +60,29 @@
<div
style="display: flex;align-items: center;gap:4px"
>
{#each schemas as schema}
<Dropdown>
<div slot="button" class:is-first={!recordId}>
{schema.label}
</div>
<Dropdown>
<div slot="button">New</div>
{#each schemas as schema}
<button
class=" button"
on:click={(e) =>
createInlineReference(e, schema.name)}
>Create New Record
>{schema.label}
</button>
{/each}
</Dropdown>
<Dropdown>
<div slot="button"> <Icon icon="magnifying-glass"/></div>
{#each schemas as schema}
<button
class="button"
on:click={(e) => openBrowseModal(e, schema.name)}
>
<Icon icon="magnifying-glass"/>
Search
</button
>
</Dropdown>
{/each}
>{schema.label}
</button>
{/each}
</Dropdown>
</div>
{:else}
<div style="display:flex;align-items: center;gap: 4px">
@@ -100,9 +103,9 @@
<DialogRecord bind:this={dialogRecord}>
{#if inLineCreateRecord}
<InlineEdit
{...inLineCreateRecord}
isCreateMode={true}
on:cancel={(e) => (inLineCreateRecord = null)}
on:inlinesaved={save}
/>
@@ -2,6 +2,7 @@
import Tinymce from "../../libs/Tinymce.svelte";
import RichEditorFiles from "./RichEditorFiles.svelte";
import {getErrorMessage} from "./errorMessage";
import Trix from "../../libs/Trix.svelte";
export let value;
export let field;
@@ -24,8 +25,8 @@
<div class="mb-0">
<Tinymce bind:this={editor} bind:value {additionalConfig}/>
<Trix {field} bind:this={editor} bind:value></Trix>
<!-- <Tinymce bind:this={editor} bind:value {additionalConfig}/>-->
{#if field.collections.length > 0}
<RichEditorFiles
bind:graph
@@ -38,7 +39,7 @@
</RichEditorFiles>
{/if}
<!-- <TipTap bind:value />-->
{#if errorMessage}
<div class="invalid-feedback d-block">
@@ -4,7 +4,7 @@
import {createEventDispatcher, getContext} from "svelte";
import Preview from "../../files/Preview.svelte";
import {previewTitle} from "./../Preview";
import {htmlurl} from "../../files/imageserver.js"
import {fileurl, htmlurl} from "../../files/imageserver.js"
import Status from "./../Status.svelte";
import Dropdown from "../../common/Dropdown.svelte";
@@ -25,8 +25,13 @@
function insert(e, preset) {
e.preventDefault();
let html = htmlurl(channel,record, preset)
dispatch("editor-insert", html);
let html = htmlurl(channel, record, preset)
dispatch("editor-insert", {
html: html,
url: channel.filesUrl + `/templates/${preset}/${record._file.path}`,
originalUrl: channel.filesUrl + "/" + record._file.path,
record: record
});
}
</script>
+2562 -587
View File
File diff suppressed because it is too large Load Diff
+3 -20
View File
@@ -11,40 +11,23 @@
"@codemirror/lang-markdown": "^6.2.5",
"@codemirror/state": "^6.4.1",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tiptap/core": "^2.6.4",
"@tiptap/extension-blockquote": "^2.6.4",
"@tiptap/extension-bold": "^2.6.4",
"@tiptap/extension-bullet-list": "^2.6.4",
"@tiptap/extension-code": "^2.6.4",
"@tiptap/extension-document": "^2.6.4",
"@tiptap/extension-heading": "^2.6.4",
"@tiptap/extension-history": "^2.6.4",
"@tiptap/extension-italic": "^2.6.4",
"@tiptap/extension-list-item": "^2.6.4",
"@tiptap/extension-ordered-list": "^2.6.4",
"@tiptap/extension-paragraph": "^2.6.4",
"@tiptap/extension-strike": "^2.6.4",
"@tiptap/extension-table": "^2.6.4",
"@tiptap/extension-table-cell": "^2.6.4",
"@tiptap/extension-table-header": "^2.6.4",
"@tiptap/extension-table-row": "^2.6.4",
"@tiptap/extension-text": "^2.6.4",
"@tiptap/extension-underline": "^2.6.4",
"@tiptap/pm": "^2.6.4",
"axios": "^1.7.4",
"codemirror": "^6.0.1",
"date-fns": "^3.6.0",
"flatpickr": "^4.6.13",
"fuse.js": "^7.0.0",
"htmx.org": "^2.0.1",
"install": "^0.13.0",
"laravel-vite-plugin": "^1.0.5",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"npm": "^10.8.2",
"postcss": "8.4.31",
"sass": "^1.77.8",
"sortablejs": "^1.15.2",
"svelte": "^4.2.18",
"tinymce": "^6.8.4",
"trix": "^2.1.5",
"uuid": "^10.0.0",
"vite": "5.2.6"
}
+1 -1
View File
@@ -16,7 +16,7 @@
overflow: visible;
position: absolute;
border-radius: 12px;
z-index: 20;
z-index: 22;
background: var(--p20);
transition: 600ms;
flex-grow: 1;
+6 -4
View File
@@ -1,6 +1,6 @@
.record-edit {
position: relative;
max-width: 900px;
.invalid-feedback {
color: var(--text-error);
font-size: 15px;
@@ -44,7 +44,7 @@
margin: 6px 0;
border-color: transparent;
.button {
.button:not(.primary) {
background: var(--p30);
&:hover {
@@ -53,9 +53,10 @@
}
dialog {
.button {
.button:not(.primary) {
background: var(--p20);
&:hover {
background: var(--p30);
}
@@ -142,4 +143,5 @@
}
}
}
}
+150
View File
@@ -0,0 +1,150 @@
.tiptap {
width: 100%;
background: var(--p20);
border: 1px solid var(--p50);
border-radius: 0 0 5px 5px;
padding: 15px 15px;
font-size: 16px;
:first-child {
margin-top: 0;
}
&:focus {
background: var(--p10);
}
img {
&.ProseMirror-selectednode {
box-shadow: 0 0 1px 2px var(--p70);
}
}
}
.editor-field {
.editor-toolbar {
display: flex;
gap: 4px;
background: var(--p30);
border-radius: 5px 5px 0 0;
padding: 5px 7px;
.button:not(.primary) {
font-weight: 700;
&.active {
background: var(--p40);
}
}
}
}
.content {
.tiptap {
li > p {
display: inline;
}
}
}
trix-editor {
background: var(--p20)!important;
border: 1px solid var(--p50)!important;
border-radius: 0 0 5px 5px!important;
padding: 15px 15px!important;
& > div {
margin-bottom: 14px;
font-size: 16px;
line-height: 23px;
}
&:focus {
background: var(--p10)!important;
}
figure.attachment{
display: flex!important;
flex-direction: column!important;
justify-content: center;
align-items: center;
gap: 10px;
}
.attachment {
background: var(--p20);
padding: 12px 0;
text-align: center;
display: flex;
justify-content: center;
img {
margin-bottom: 0;
}
}
[data-trix-mutable].attachment img {
box-shadow: 0 0 1px 2px var(--p70) !important;
}
.trix-button--remove {
box-shadow: none !important;
border: 2px solid var(--p40) !important;
}
.trix-button--remove:hover {
border: 2px solid var(--p40);
}
a {
color: var(--p80);
}
}
trix-toolbar {
.trix-button-row {
display: flex;
}
.trix-button-group {
background: transparent !important;
border: none !important;
display: flex !important;
gap: 4px;
}
.trix-button-group--history-tools,.trix-button-group--file-tools
{
display: none !important;
}
.trix-button {
border-radius: 6px !important;
background: var(--p30) !important;
padding: 14px 22px !important;
margin: 0 !important;
cursor: pointer;
border: 0px solid var(--p30) !important;
font-size: 14px !important;
min-height: 27px !important;
display: flex !important;
align-items: center !important;
gap: 4px;
color: var(--text) !important;
&:before{
background-size: 22px!important;
}
&:hover{
background: var(--p40) !important;
}
&.trix-active{
background: var(--p50) !important;
}
}
}
+5 -1
View File
@@ -29,10 +29,14 @@
align-items: center;
background: var(--p30);
font-size: 16px;
padding: 3px 12px 6px;
padding: 3px 12px 3px;
color: var(--text);
border: none;
border-radius: 12px;
&:focus{
box-shadow: none;
}
&:hover {
background: var(--p40);
+1
View File
@@ -3,6 +3,7 @@
margin: 20px 0 20px;
display: flex;
gap: 4px;
flex-wrap: wrap;
.tab{
list-style: none;
+57
View File
@@ -12,6 +12,15 @@
margin-bottom: 0;
}
}
h1{
font-size: 24px;
line-height: 34px;
}
h2{
font-size: 20px;
line-height: 30px;
}
ul {
padding: 0 0 0 16px;
@@ -31,6 +40,54 @@
}
}
code{
background: var(--p30);
padding: 0 6px;
border-radius: 12px;
}
img{
margin-bottom: 14px;
}
blockquote{
border:1px solid var(--p30);
border-radius: 12px;
padding: 12px 40px;
position: relative;
&::before{
content: "\201C";
color: var(--p60);
font-size:4em;
position: absolute;
left: 10px;
top: 20px;
}
&::after{
content: '';
}
}
pre {
background: var(--grey-light);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
}
.lx-small-text {
+4
View File
@@ -53,6 +53,7 @@
@import "./table";
@import "./avatar";
@import "./codemirror";
@import "./rich";
@import "./layout";
@import "./wrappers";
@import "./toolbar";
@@ -104,3 +105,6 @@ a {
position: relative;
}
.flatpickr-wrapper {
display: block!important;
}
+1
View File
@@ -10,6 +10,7 @@
<h2 class="mb-5">Enter Lucent</h2>
<form hx-post="/lucent/login" >
@csrf
<p>Submit your email address and you will receive a <b>login link</b> to your email</p>
<p>Don't forget to check your spam folder</p>
<div class="mt-5 mb-3">
+15 -3
View File
@@ -2,6 +2,7 @@
namespace Lucent\Channel;
use Lucent\Channel\Data\UserCommand;
use Lucent\Primitive\Collection;
use Lucent\Schema\Schema;
@@ -13,20 +14,31 @@ final class Channel
/**
* @param Collection<Schema> $schemas
* @param Collection<UserCommand> $commands
*/
function __construct(
public string $name,
public string $url,
public string $previewTarget,
public string $generateCommand,
public Collection $commands,
public Collection $schemas,
public array $imageFilters,
public array $roles,
)
{
$this->lucentUrl = $url . "/lucent";
$this->filesUrl = $url . "/storage";
$this->previewTargetUrl = $url . "/". $previewTarget;
$this->filesUrl = $this->makeFilesUrl();
$this->previewTargetUrl = $url . "/" . $previewTarget;
}
private function makeFilesUrl(): string
{
return match (config("filesystems.disks.lucent.driver")) {
"s3" => config("filesystems.disks.lucent.endpoint") . "/" . config("filesystems.disks.lucent.bucket"),
"local" => $this->url . "/storage" . config("filesystems.disks.lucent.endpoint"),
};
}
}
+11 -5
View File
@@ -3,6 +3,7 @@
namespace Lucent\Channel;
use Lucent\Channel\Data\UserCommand;
use Lucent\Primitive\Collection;
use Lucent\Schema\Schema;
use Lucent\Schema\SchemaService;
@@ -13,7 +14,7 @@ final class ChannelService
public Channel $channel;
private function __construct(
public SchemaService $schemaService
public SchemaService $schemaService,
)
{
@@ -29,11 +30,16 @@ final class ChannelService
$schemaService = new SchemaService();
$schemasCollection = (new Collection($schemasArray["schemas"] ?? []))->map([$schemaService, 'fromArray']);
$userCommands = [];
foreach (config("lucent.commands") ?? [] as $signature => $desc) {
$userCommands[] = new UserCommand($desc, $signature);
}
$channel = new Channel(
name: config("lucent.name") ?? "",
url: rtrim(config("lucent.url") ?? "", "/"),
previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"),
generateCommand: config("lucent.generateCommand") ?? "",
commands: Collection::make($userCommands),
schemas: $schemasCollection,
imageFilters: config("lucent.imageFilters") ?? [],
roles: $schemasArray["roles"] ?? []
@@ -63,7 +69,7 @@ final class ChannelService
*/
public function schemasReadableByRoles(array $roles): array
{
$schemasAllRead = $this->channel->schemas->filter(fn(Schema $schema) => empty($schema->read))->values()->pluck("name");
$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();
@@ -76,8 +82,8 @@ final class ChannelService
*/
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");
$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();
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Channel\Data;
class UserCommand
{
public function __construct(
public string $name,
public string $signature,
)
{
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace Lucent\Command;
use Illuminate\Support\Facades\DB;
use Lucent\Command\Data\CommandLogItem;
class CommandRepo
{
public function findBySignature($signature): ?CommandLogItem
{
$row = DB::table("command_logs")->where("signature", $signature)->first();
if (empty($row)) {
return null;
}
return CommandLogItem::fromDB($row);
}
public function upsertCommand(CommandLogItem $commandLogItem): void
{
$foundCommandLogItem = $this->findBySignature($commandLogItem->signature);
if (empty($foundCommandLogItem)) {
DB::table("command_logs")->insert(toArray($commandLogItem));
return;
}
DB::table("command_logs")->where("signature", $commandLogItem->signature)->update(toArray($commandLogItem));
}
public function appendToLogs(string $signature, string $line): void
{
$res = DB::update(
'update command_logs set logs = logs || ? where signature = ?',
[$line, $signature]
);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Lucent\Command;
use Lucent\Command\Data\CommandLogItem;
use Lucent\Id\Id;
use Lucent\LucentException;
use Symfony\Component\Process\PhpExecutableFinder;
class CommandService
{
public function __construct(
private CommandRepo $commandRepo,
)
{
}
public function run(string $signature): CommandLogItem
{
$commandLogItem = $this->commandRepo->findBySignature($signature);
if (empty($commandLogItem)) {
$commandLogItem = new CommandLogItem(
id: Id::new(),
signature: $signature,
pid: null,
logs: ""
);
} elseif ($this->commandIsRunning($commandLogItem->pid)) {
throw new LucentException('Command is already running');
}
$commandLogItem->pid = $this->runCommand($signature);
$commandLogItem->logs = "";
$this->commandRepo->upsertCommand($commandLogItem);
return $commandLogItem;
}
public function logWriter(string $signature): callable
{
return function (string $line) use($signature) {
$this->commandRepo->appendToLogs($signature, $line.PHP_EOL);
};
}
private function runCommand(string $signature): int
{
$phpBinaryFinder = new PhpExecutableFinder();
$phpBinaryPath = $phpBinaryFinder->find();
$pid = (int)shell_exec("cd " . base_path() . " && $phpBinaryPath artisan {$signature} > /dev/null 2>&1 & echo $!");
return $pid;
}
public function commandIsRunning(int $pid): bool
{
return file_exists("/proc/$pid");
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Lucent\Command\Data;
use stdClass;
class CommandLogItem
{
public function __construct(
public string $id,
public string $signature,
public ?int $pid,
public string $logs,
)
{
}
public static function fromDB(stdClass $data): self
{
return new self(
id: $data->id,
signature: $data->signature,
pid: $data->pid,
logs: $data->logs,
);
}
}
+3 -9
View File
@@ -16,19 +16,13 @@ class CompileSchemas extends Command
protected $description = 'Compiles schemas';
public function __construct(
public SchemaService $schemaService
)
public function handle(SchemaService $schemaService)
{
parent::__construct();
}
public function handle()
{
$configDir = base_path(config('lucent.schemas_path'));
$schemasDirIterator = new DirectoryIterator($configDir);
$schemas = [];
foreach ($schemasDirIterator as $file) {
if ($file->getExtension() !== "json") {
continue;
@@ -46,7 +40,7 @@ class CompileSchemas extends Command
$schemas = collect($schemas)->sortBy("label")->values();
$roles = $schemas
->map([$this->schemaService, 'fromArray'])
->map([$schemaService, 'fromArray'])
->whereIn("type", [Type::COLLECTION, Type::FILES])
->reduce(fn($carry, Schema $schema) => array_merge(
$carry,
+40 -6
View File
@@ -1,15 +1,49 @@
<?php
return [
"env" => env("LUCENT_ENV", "production"),
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION',"sqlite")),
"name" => env("LUCENT_NAME", "Lucent"),
"env" => env("LUCENT_ENV", "production"),
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "app/Lucent"),
"database" => env('LUCENT_DB_CONNECTION', env('DB_CONNECTION', "sqlite")),
"name" => env("LUCENT_NAME", "Lucent"),
"url" => env("LUCENT_URL", env('APP_URL')),
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
"generateCommand" => env("LUCENT_GENERATE_COMMAND", "generate:static"),
"previewTarget" => env("LUCENT_PREVIEW_TARGET", "previewTarget"),
/*
* Make available laravel artisan commands for admin users
* example:
* [
* "command1:signature" => "Description 1"
* "command2:signature" => "Description 2"
* ]
*
* */
"commands" => [],
/*
* Image filter will be available both for rich editor fields
* and throughout your application
*
* example:
* [
* "filterName" => Filter::class
* ]
*
* */
"imageFilters" => [],
"canInvite" => ["admin"],
"canBuild" => ["admin"],
"systemUserId" => "",
"schemaFields" => [
\Lucent\Schema\Ui\Checkbox::class,
\Lucent\Schema\Ui\Color::class,
\Lucent\Schema\Ui\Date::class,
\Lucent\Schema\Ui\Datetime::class,
\Lucent\Schema\Ui\File::class,
\Lucent\Schema\Ui\Json::class,
\Lucent\Schema\Ui\Markdown::class,
\Lucent\Schema\Ui\Number::class,
\Lucent\Schema\Ui\Reference::class,
\Lucent\Schema\Ui\Rich::class,
\Lucent\Schema\Ui\Slug::class,
\Lucent\Schema\Ui\Text::class,
\Lucent\Schema\Ui\Textarea::class
]
];
@@ -5,6 +5,9 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
@@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
@@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
@@ -5,6 +5,9 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
@@ -14,7 +17,7 @@ return new class extends Migration {
{
Schema::table('records', function (Blueprint $table) {
$table->dropIndex(['schema', 'status']);
$table->index(['schema', '_sys->updatedAt','status']);
$table->index(['schema', '_sys->updatedAt', 'status']);
});
}
@@ -27,7 +30,7 @@ return new class extends Migration {
{
Schema::table('records', function (Blueprint $table) {
$table->dropIndex(['schema', '_sys->updatedAt','status']);
$table->dropIndex(['schema', '_sys->updatedAt', 'status']);
$table->index(['schema', 'status']);
});
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
protected $connection = 'lucentdb';
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('command_logs', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('signature');
$table->integer('pid')->nullable();
$table->text('logs');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('command_logs');
}
};
+10 -11
View File
@@ -14,7 +14,6 @@ use Lucent\LucentException;
use Lucent\Record\FileData as RecordFile;
use Lucent\Record\QueryRecord;
use Lucent\Schema\FilesSchema;
use Lucent\Schema\Schema;
use Spatie\ImageOptimizer\OptimizerChainFactory;
class FileService
@@ -28,7 +27,7 @@ class FileService
public function getPath(QueryRecord $file): string
{
return $this->channelService->channel->url . "/storage/" . $file->_file->path;
return $this->channelService->channel->filesUrl . "/" . $file->_file->path;
}
public function createFromUrl(FilesSchema $schema, string $url): FileUploadResult
@@ -111,15 +110,16 @@ class FileService
public function loadDisk(): Filesystem
{
return Storage::disk('lucent');
return Storage::build([
'driver' => 'local',
'driver' => 'lucent',
// 'key' => config("filesystems.disks.s3.key"),
// 'secret' => config("filesystems.disks.s3.secret"),
// 'region' => config("filesystems.disks.s3.region"),
// 'bucket' => config("filesystems.disks.s3.bucket"),
// // 'url' => $schema->objectStorageUrl,
// 'endpoint' => $schema->objectStorageEndpoint,
'use_path_style_endpoint' => false,
// 'use_path_style_endpoint' => false,
'visibility' => 'public', // now managed by aws policy
'root' => storage_path('app/public'),
'throw' => true,
@@ -140,12 +140,10 @@ class FileService
private function createThumbnail(Filesystem $disk, string $schemaPath, string $filename, UploadedFile $file): void
{
$thumbDir = storage_path("app/public/thumbs/" . $schemaPath . "/");
if (!file_exists($thumbDir)) {
make_dir_r($thumbDir);
}
$thumbDir = "thumbs/" . $schemaPath . "/";
// if (!file_exists($thumbDir)) {
// make_dir_r($thumbDir);
// }
try {
ImageManagerStatic::configure(['driver' => 'imagick']);
@@ -157,7 +155,8 @@ class FileService
$image->fit(300, 300);
try {
$image->encode('webp', 75)->save($thumbDir . $filename);
$this->loadDisk()->put($thumbDir . $filename, $image->encode('webp', 75));
// $image->encode('webp', 75)->save($thumbDir . $filename);
} catch (Exception $e) {
logger($e->getMessage());
}
+5 -20
View File
@@ -15,6 +15,7 @@ class ImageService
public function __construct(
public ImageManager $imageManager,
public FileService $fileService,
public ChannelService $channelService,
public Logger $logger
)
@@ -50,28 +51,18 @@ class ImageService
private function createTemplate(string $originalPath, string $template): string
{
$originalFilePath = public_path("storage/" . $originalPath);
$templateUri = "/templates/" . $template . "/" . $originalPath;
$templateFilePath = public_path("storage/" . $templateUri);
if (!file_exists($originalFilePath)) {
return $this->notFoundImage;
}
if (!file_exists(pathinfo($templateFilePath, PATHINFO_DIRNAME))) {
$this->make_dir(pathinfo($templateFilePath, PATHINFO_DIRNAME));
}
try {
$image = $this->imageManager->make($originalFilePath);
$image = $this->imageManager->make( $this->fileService->loadDisk()->get($originalPath));
} catch (Exception $e) {
$this->logger->error($e->getMessage());
return $this->notFoundImage;
return $originalPath;
}
$image = $image->filter(new $this->channelService->channel->imageFilters[$template]);
try {
$image = $this->imageManager->make((string)$image->encode('webp', 75));
$image->save($templateFilePath);
$templateUri = "/templates/" . $template . "/" . $originalPath;
$this->fileService->loadDisk()->put($templateUri, $image->encode('webp', 75));
} catch (Exception $e) {
$this->logger->error($e->getMessage());
return $this->notFoundImage;
@@ -80,10 +71,4 @@ class ImageService
return $templateUri;
}
private function make_dir(string $path): void
{
is_dir($path) || mkdir($path, 0777, true);
}
}
+25 -28
View File
@@ -5,64 +5,62 @@ namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Artisan;
use Lucent\Channel\ChannelService;
use Lucent\Command\CommandRepo;
use Lucent\Command\CommandService;
use Lucent\Svelte\Svelte;
use function Lucent\Response\ok;
class BuildController extends Controller
{
public function __construct(
public readonly Svelte $svelte,
public readonly ChannelService $channelService,
private readonly Svelte $svelte,
private readonly ChannelService $channelService,
private readonly CommandService $commandService,
private readonly CommandRepo $commandRepo,
)
{
}
public function build()
public function build(Request $request)
{
$commandSignature = $request->route("signature");
$this->commandService->run($commandSignature);
$buildLogFile = storage_path("lucent/build.log");
if(file_exists($buildLogFile)){
unlink($buildLogFile);
}
exec("cd " . base_path() . " && php8.3 artisan {$this->channelService->channel->generateCommand} > " . $buildLogFile . " 2>&1 & echo $!", $op);
$pid = (int)$op[0];
return redirect($this->channelService->channel->lucentUrl . "/build-report");
}
public function report(): View
public function report(Request $request): View
{
$commandSignature = $request->route("signature");
$command = $this->channelService->channel->commands->firstWhere("signature", $commandSignature);
return $this->svelte->render(
layout: "channel",
view: "buildReport",
title: "Build Report",
title: $command->name,
data: [
"command" => $command,
]
);
}
public function reportSource()
public function reportSource(Request $request)
{
return response()->stream(function () {
$commandSignature = $request->route("signature");
return response()->stream(function () use ($commandSignature) {
while (true) {
// sleep(1); // 50ms
$commandLogItem = $this->commandRepo->findBySignature($commandSignature);
$data["date"] = date("Y-m-d H:i:s");
$data["logs"] = file_get_contents(storage_path("lucent/build.log"));
$data["logs"] = $commandLogItem->logs ?? "";
// $lines = explode("\n",$data["logs"]);
echo 'data: ' .json_encode($data);
echo 'data: ' . json_encode($data);
echo "\n\n";
ob_flush();
flush();
if(str_contains($data["logs"],"Finito")){
break;
}
if(str_contains($data["logs"],"Exception")){
logger($this->commandService->commandIsRunning($commandLogItem->pid));
if (!$this->commandService->commandIsRunning($commandLogItem->pid)) {
break;
}
@@ -79,5 +77,4 @@ class BuildController extends Controller
]);
}
}
+13 -12
View File
@@ -8,7 +8,7 @@ use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Query\Operator;
use Lucent\Query\Operator\OperatorRegistry;
use Lucent\Query\Query;
use Lucent\Record\InputData\EdgeInputData;
use Lucent\Record\InputData\RecordInputData;
@@ -25,13 +25,14 @@ use function Lucent\Response\ok;
class RecordController extends Controller
{
public function __construct(
private readonly RecordService $recordService,
private readonly AccountService $accountService,
private readonly AuthService $authService,
private readonly ChannelService $channelService,
private readonly Svelte $svelte,
private readonly Query $query,
private readonly Manager $recordManager
private readonly RecordService $recordService,
private readonly AccountService $accountService,
private readonly AuthService $authService,
private readonly ChannelService $channelService,
private readonly Svelte $svelte,
private readonly Query $query,
private readonly Manager $recordManager,
private readonly OperatorRegistry $operatorRegistry
)
{
}
@@ -79,7 +80,6 @@ class RecordController extends Controller
$records = $graph->getRootRecords()->toArray();
$data = [
"schemas" => $this->channelService->channel->schemas,
"schema" => $schema,
@@ -87,7 +87,7 @@ class RecordController extends Controller
"records" => $records,
"graph" => toArray($graph),
"systemFields" => array_values(System::list()),
"operators" => array_values(Operator::list()),
"operators" => $this->operatorRegistry->all(),
"sortParam" => $sort,
"sortField" => $schema->fields->merge(array_values(System::list()))->firstWhere(fn($field) => $field->name === $sort || "-" . $field->name === $sort || "data." . $field->name === $sort || "-data." . $field->name === $sort),
"limit" => $limit,
@@ -165,7 +165,7 @@ class RecordController extends Controller
$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());
$record = $this->recordService->createEmpty($schema);
$queryRecord = QueryRecord::fromRecord($record);
return $this->svelte->render(
layout: "channel",
@@ -200,6 +200,7 @@ class RecordController extends Controller
"schema" => $schema,
"record" => $queryRecord,
"isCreateMode" => true,
"isWritable" => in_array($record->schema, $this->accountService->currentWritableSchemas())
];
}
@@ -302,7 +303,7 @@ class RecordController extends Controller
id: $request->input("record.id"),
data: $request->input("record.data"),
status: Status::from($request->input("record.status")),
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
edges: array_map(EdgeInputData::fromArray(...), $request->input("edges") ?? []),
);
}
+3 -3
View File
@@ -33,9 +33,9 @@ Route::group([
Route::get('/profile', [AccountController::class, 'profile']);
Route::post('/account/update-name', [AccountController::class, 'updateName']);
Route::post('/account/update-email', [AccountController::class, 'updateEmail']);
Route::get('/build-report', [BuildController::class, 'report']);
Route::get('/build-report-source', [BuildController::class, 'reportSource']);
Route::post('/build', [BuildController::class, 'build']);
Route::get('/command-report/{signature}', [BuildController::class, 'report']);
Route::get('/command-report-source/{signature}', [BuildController::class, 'reportSource']);
Route::post('/command/{signature}', [BuildController::class, 'build']);
});
-15
View File
@@ -1,15 +0,0 @@
<?php
namespace Lucent;
use Illuminate\Support\ServiceProvider;
use Lucent\Edge\Event\EdgesUpdated;
class LucentEventServiceProvider extends ServiceProvider
{
protected $listen = [
EdgesUpdated::class => [
SendEmailVerificationNotification::class,
],
];
}
+4 -7
View File
@@ -12,7 +12,6 @@ use Lucent\Commands\CompileSchemas;
use Lucent\Commands\LiveLink;
use Lucent\Commands\RebuildThumbnails;
use Lucent\Commands\RemoveOrphanEdges;
use Lucent\Event\Dispatcher;
use Lucent\File\FileService;
use Lucent\File\ImageService;
use Lucent\Query\DatabaseGraph\DatabaseGraph;
@@ -30,22 +29,20 @@ class LucentServiceProvider extends ServiceProvider
return ChannelService::fromConfig();
});
$this->app->singleton(Dispatcher::class, function () {
return new Dispatcher();
});
$this->app->bind(ImageManager::class, function () {
return new ImageManager(['driver' => 'imagick']);
});
$this->app->bind(DatabaseGraph::class, function () {
return match (config("lucent.database")) {
$dbConnection = config("lucent.database");
return match (config("database.connections.$dbConnection.driver")) {
"sqlite" => new SqliteDatabaseGraph(),
"pgsql" => new PgsqlDatabaseGraph(),
};
});
$this->app->register(LucentEventServiceProvider::class);
}
@@ -0,0 +1,13 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Lucent\Query\Filter\Argument;
class BuilderConverter
{
public function for(Argument $argument): IBuilderConverter
{
return new ($argument->operator->converter)($argument);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class Equals implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, $this->formatValue());
}
private function formatValue(): string
{
return trim($this->argument->value);
}
}
@@ -0,0 +1,26 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class EqualsFalse implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, false);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, false);
}
}
@@ -0,0 +1,31 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class EqualsNumber implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, $this->formatValue());
}
private function formatValue(): int|float
{
$value = trim($this->argument->value);
return str_contains($value, ".") ? floatval($value) : intval($value);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class EqualsTrue implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, true);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, true);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class Exists implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNot($this->argument->field, "")->whereNotNull($this->argument->field);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->whereNot($this->argument->field, "")->whereNotNull($this->argument->field);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class Filter implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereJsonContains($this->argument->field, [$this->argument->value]);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereJsonContains($this->argument->field, [$this->argument->value]);
}
}
@@ -0,0 +1,35 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class GreaterThan implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, ">", $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, ">", $this->formatValue());
}
private function formatValue(): int|float
{
$value = trim($this->argument->value);
if (is_numeric($value)) {
return str_contains($value, ".") ? floatval($value) : intval($value);
}
return $value;
}
}
@@ -0,0 +1,35 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class GreaterThanEquals implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, ">=", $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, ">=", $this->formatValue());
}
private function formatValue(): int|float
{
$value = trim($this->argument->value);
if (is_numeric($value)) {
return str_contains($value, ".") ? floatval($value) : intval($value);
}
return $value;
}
}
@@ -0,0 +1,13 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
interface IBuilderConverter
{
public function toAndQueryBuilder(Builder $builder): Builder;
public function toOrQueryBuilder(Builder $builder): Builder;
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class In implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereIn($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereIn($this->argument->field, $this->formatValue());
}
private function formatValue(): array
{
$value = $this->argument->value;
if (is_string($value)) {
$value = explode(",", $value);
}
return array_map(fn($v) => trim($v), $value);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class InNum implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereIn($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereIn($this->argument->field, $this->formatValue());
}
private function formatValue(): array
{
$value = $this->argument->value;
if (is_string($value)) {
$value = explode(",", $value);
}
return array_map(fn($v) => $this->formatNumber($v), $value);
}
private function formatNumber(string $value): float|int
{
$value = trim($value);
return str_contains($value, ".") ? floatval($value) : intval($value);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class IsNull implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNull($this->argument->field);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNull($this->argument->field);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class LessThan implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, "<", $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, "<", $this->formatValue());
}
private function formatValue(): int|float
{
$value = trim($this->argument->value);
if (is_numeric($value)) {
return str_contains($value, ".") ? floatval($value) : intval($value);
}
return $value;
}
}
@@ -0,0 +1,35 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class LessThanEquals implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, "<=", $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, "<=", $this->formatValue());
}
private function formatValue(): int|float
{
$value = trim($this->argument->value);
if (is_numeric($value)) {
return str_contains($value, ".") ? floatval($value) : intval($value);
}
return $value;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotEquals implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNot($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNot($this->argument->field, $this->formatValue());
}
private function formatValue(): string
{
return trim($this->argument->value);
}
}
@@ -0,0 +1,26 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotEqualsFalse implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNot($this->argument->field, false);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNot($this->argument->field, false);
}
}
@@ -0,0 +1,30 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotEqualsNumber implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNot($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNot($this->argument->field, $this->formatValue());
}
private function formatValue(): int|float
{
$value = trim($this->argument->value);
return str_contains($value, ".") ? floatval($value) : intval($value);
}
}
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotEqualsTrue implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNot($this->argument->field, true);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNot($this->argument->field, true);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotExists implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where(fn($b) => $b->where($this->argument->field, "")->orWhereNull($this->argument->field));
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere(fn($b) => $b->where($this->argument->field, "")->orWhereNull($this->argument->field));
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotIn implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNotIn($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNotIn($this->argument->field, $this->formatValue());
}
private function formatValue(): array
{
$value = $this->argument->value;
if (is_string($value)) {
$value = explode(",", $value);
}
return array_map(fn($v) => trim($v), $value);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotInNum implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNotIn($this->argument->field, $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNotIn($this->argument->field, $this->formatValue());
}
private function formatValue(): array
{
$value = $this->argument->value;
if (is_string($value)) {
$value = explode(",", $value);
}
return array_map(fn($v) => $this->formatNumber($v), $value);
}
private function formatNumber(string $value): float|int
{
$value = trim($value);
return str_contains($value, ".") ? floatval($value) : intval($value);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class NotNull implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->whereNotNull($this->argument->field);
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhereNotNull($this->argument->field);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Lucent\Query\BuilderConverter;
use Illuminate\Database\Query\Builder;
use Lucent\Query\Filter\Argument;
readonly class Regex implements IBuilderConverter
{
public function __construct(private Argument $argument)
{
}
public function toAndQueryBuilder(Builder $builder): Builder
{
return $builder->where($this->argument->field, "like", $this->formatValue());
}
public function toOrQueryBuilder(Builder $builder): Builder
{
return $builder->orWhere($this->argument->field, "like", $this->formatValue());
}
private function formatValue(): string
{
return "%" . strtolower(trim($this->argument->value)) . "%";
}
}
+8 -4
View File
@@ -2,11 +2,15 @@
namespace Lucent\Query\Filter;
use Lucent\Query\Operator\Operator;
class Argument
{
public function __construct(
public string $field,
public string $operator,
public mixed $value,
){}
public string $field,
public Operator $operator,
public mixed $value,
)
{
}
}
+16 -139
View File
@@ -5,19 +5,26 @@ namespace Lucent\Query;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Lucent\Query\BuilderConverter\BuilderConverter;
use Lucent\Query\Filter\AndFilter;
use Lucent\Query\Filter\Argument;
use Lucent\Query\Filter\Filter;
use Lucent\Query\Filter\OrFilter;
use Lucent\Query\Operator\In;
use Lucent\Query\Operator\OperatorDetector;
use function explode;
final class FilterParser
{
public function __construct(public Application $app)
public function __construct(
public Application $app,
public BuilderConverter $builderConverter,
public OperatorDetector $operatorDetector,
)
{
}
/**
* @param array $arguments
* @return array<Argument>
@@ -25,105 +32,11 @@ final class FilterParser
private function formatArguments(array $arguments): array
{
return collect($arguments)->reduce(function ($c, $v, $k) {
$c[] = $this->formatArgument($v, $k);
$c[] = $this->operatorDetector->detect($v, $k);
return $c;
}, []);
}
private function formatArgument(mixed $value, string $filter): Argument
{
$operator = $this->detectOperator($filter);
$field = $this->detectField($filter, $operator);
$formattedValue = match ($operator) {
"eq" => $this->formatText($value),
"ne" => $this->formatText($value),
"eqnum" => $this->formatNumber($value),
"nenum" => $this->formatNumber($value),
"object" => $this->formatText($value),
"in" => $this->formatListString($value),
"nin" => $this->formatListString($value),
"innum" => $this->formatListNum($value),
"ninnum" => $this->formatListNum($value),
"eqtrue" => true,
"eqfalse" => false,
"netrue" => true,
"nefalse" => false,
"regex" => "%" . strtolower($value) . "%",
"gt" => is_numeric($value) ? floatval($value) : $value,
"gte" => is_numeric($value) ? floatval($value) : $value,
"lt" => is_numeric($value) ? floatval($value) : $value,
"lte" => is_numeric($value) ? floatval($value) : $value,
"null" => null,
"nnull" => null,
"exists" => true,
"nexists" => false,
default => $value,
};
$matchedOperator = Operator::list()[$operator];
return new Argument(
field: str_replace(".", "->", $field),
operator: $matchedOperator->db,
value: $formattedValue
);
}
private function formatText(string $value): string
{
return trim($value);
}
private function formatNumber(string $value): float
{
return floatval($value);
}
private function formatListString(mixed $value): array
{
if (is_string($value)) {
$value = explode(",", $value);
}
return array_map(fn($v) => $this->formatText($v), $value);
}
private function formatListNum(mixed $value): array
{
if (\is_string($value)) {
$value = explode(",", $value);
}
return \array_map(fn($v) => $this->formatNumber($v), $value);
}
private function detectOperator(string $filter): string
{
$exploded = \explode("_", $filter);
$candidate = end($exploded);
$operatorsListNames = collect(Operator::list())->map(fn($o) => $o->name)->toArray();
if (\in_array($candidate, $operatorsListNames)) {
return $candidate;
}
return 'eq';
}
private function detectField(string $filter, string $operator): string
{
$exploded = explode("_", $filter);
$candidate = array_pop($exploded);
if ($candidate === $operator) {
return implode("_", $exploded);
}
return $filter;
}
private function formatReferences(array $referenceArguments): Argument
{
$subqueries = collect($referenceArguments)->reduce(function ($c, $v, $k) {
@@ -151,7 +64,7 @@ final class FilterParser
return new Argument(
field: "id",
operator: "in",
operator: new In(),
value: $sourceIds
);
@@ -174,7 +87,6 @@ final class FilterParser
private function parseArguments(Filter $arguments): array
{
[$normalArguments, $referenceArguments] = $this->separateMainFromReferenceArguments($arguments);
$formattedArguments = $this->formatArguments($normalArguments);
if (!empty($referenceArguments)) {
$formattedArguments[] = $this->formatReferences($referenceArguments);
@@ -198,26 +110,8 @@ final class FilterParser
*/
private function parseAnd(Builder $builder, array $arguments): Builder
{
foreach ($arguments as $argument) {
if ($argument->operator == "in") {
$builder->whereIn($argument->field, $argument->value);
} else if ($argument->operator == "nin") {
$builder->whereNotIn($argument->field, $argument->value);
} else if ($argument->operator == "exists") {
$builder->where($argument->field, "!=", "");
$builder->where($argument->field, "!=", null);
} elseif ($argument->operator == "filter") {
$builder->whereJsonContains($argument->field, [$argument->value]);
// target result
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
// $query->whereJsonContains($filter["field"], [$objectFilters]);
} else {
$builder->where($argument->field, $argument->operator, $argument->value);
}
}
return $builder;
return collect($arguments)
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toAndQueryBuilder($cBuilder), $builder);
}
/**
@@ -225,27 +119,10 @@ final class FilterParser
*/
private function parseOr(Builder $builder, array $arguments): Builder
{
$builder->where(function (Builder $orBuilder) use ($arguments) {
foreach ($arguments as $argument) {
if ($argument->operator == "in") {
$orBuilder->orWhereIn($argument->field, $argument->value);
} else if ($argument->operator == "nin") {
$orBuilder->orWhereNotIn($argument->field, $argument->value);
} else if ($argument->operator == "exists") {
$orBuilder->where($argument->field, "!=", "");
$orBuilder->where($argument->field, "!=", null);
} elseif ($argument->operator == "filter") {
$orBuilder->whereJsonContains($argument->field, [$argument->value]);
// target result
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
// $query->whereJsonContains($filter["field"], [$objectFilters]);
} else {
$orBuilder->orWhere($argument->field, $argument->operator, $argument->value);
}
}
return $builder->where(function (Builder $orBuilder) use ($arguments) {
return collect($arguments)
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toOrQueryBuilder($cBuilder), $orBuilder);
});
return $builder;
}
-186
View File
@@ -1,186 +0,0 @@
<?php
namespace Lucent\Query;
final class Operator
{
/**
* @psalm-param string[] $uis
*/
public function __construct(
public string $name,
public string $label,
public string $symbol,
public string $db,
public array $uis,
)
{
}
/**
* @return array<string, Operator>
*/
public static function list(): array
{
return [
"regex" => new Operator(
name: "regex",
label: "Search",
symbol: "~",
db: 'like',
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
),
"eq" => new Operator(
name: "eq",
label: "Equals",
symbol: "is",
db: '=',
uis: ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"],
),
"ne" => new Operator(
name: "ne",
label: "Not Equals",
symbol: "is not",
db: '!=',
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
),
"eqnum" => new Operator(
name: "eqnum",
label: "Equals number",
symbol: "is",
db: '=',
uis: ["number"],
),
"neqnum" => new Operator(
name: "nenum",
label: "Not Equals number",
symbol: "is not",
db: '$ne',
uis: ["number"],
),
"filter" => new Operator(
name: "filter",
label: "Equals Object",
symbol: "is",
db: 'filter',
uis: [],
),
"eqtrue" => new Operator(
name: "eqtrue",
label: "Equals true",
symbol: "is",
db: '=',
uis: ["checkbox"],
),
"eqfalse" => new Operator(
name: "eqfalse",
label: "Equals false",
symbol: "is not",
db: '=',
uis: ["checkbox"],
),
"netrue" => new Operator(
name: "netrue",
label: "Not equals true",
symbol: "!=",
db: '$ne',
uis: ["checkbox"],
),
"nefalse" => new Operator(
name: "nefalse",
label: "Not equals false",
symbol: "!=",
db: '$ne',
uis: ["checkbox"],
),
"in" => new Operator(
name: "in",
label: "In list",
symbol: "in",
db: 'in',
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
),
"innum" => new Operator(
name: "innum",
label: "In list of numbers",
symbol: "in",
db: '$in',
uis: ["number"],
),
"nin" => new Operator(
name: "nin",
label: "Not in list",
symbol: "not in",
db: 'nin',
uis: ["id", "text", "textarea", "url", "color", "date", "datetime"],
),
"ninnum" => new Operator(
name: "ninnum",
label: "Not In list of numbers",
symbol: "not in",
db: '$nin',
uis: ["number"],
),
"lt" => new Operator(
name: "lt",
label: "Less than",
symbol: "<",
db: '<',
uis: ["number", "date", "datetime"],
),
"lte" => new Operator(
name: "lte",
label: "Less than equals",
symbol: "<=",
db: '<=',
uis: ["number", "date", "datetime"],
),
"gt" => new Operator(
name: "gt",
label: "Greater than",
symbol: ">",
db: '>',
uis: ["number", "date", "datetime"],
),
"gte" => new Operator(
name: "gte",
label: "Greater than equals",
symbol: ">=",
db: '>=',
uis: ["number", "date", "datetime"],
),
"null" => new Operator(
name: "null",
label: "Is null",
symbol: "=",
db: '$eq',
uis: ["*"],
),
"nnull" => new Operator(
name: "nnull",
label: "Not null",
symbol: "!=",
db: '$ne',
uis: ["*"],
),
"exists" => new Operator(
name: "exists",
label: "Exists",
symbol: "exists",
db: 'exists',
uis: ["*"],
),
"nexists" => new Operator(
name: "nexists",
label: "Not exists",
symbol: "not exists",
db: '$exists',
uis: ["*"],
),
];
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class Equals extends Operator
{
public string $name = "eq";
public string $label = "Equals";
public string $symbol = "is";
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\Equals::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class EqualsFalse extends Operator
{
public string $name = "eqfalse";
public string $label = "Equals False";
public string $symbol = "is";
public array $uis = ["checkbox"];
public bool $hasValue = false;
public string $converter = \Lucent\Query\BuilderConverter\EqualsFalse::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class EqualsNumber extends Operator
{
public string $name = "eqnum";
public string $label = "Equals Number";
public string $symbol = "is";
public array $uis = ["number"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\EqualsNumber::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class EqualsTrue extends Operator
{
public string $name = "eqtrue";
public string $label = "Equals True";
public string $symbol = "is";
public array $uis = ["checkbox"];
public bool $hasValue = false;
public string $converter = \Lucent\Query\BuilderConverter\EqualsTrue::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class Exists extends Operator
{
public string $name = "exists";
public string $label = "Exists";
public string $symbol = "exists";
public array $uis = ["*"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\Exists::class;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Lucent\Query\Operator;
class Filter extends Operator
{
public string $name = "filter";
public string $label = "Equals Object";
public string $symbol = "is";
public array $uis = [];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\Filter::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class GreaterThan extends Operator
{
public string $name = "gt";
public string $label = "Greater than";
public string $symbol = ">";
public array $uis = ["number", "date", "datetime"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\GreaterThan::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class GreaterThanEquals extends Operator
{
public string $name = "gte";
public string $label = "Greater than equals";
public string $symbol = ">";
public array $uis = ["number", "date", "datetime"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\GreaterThanEquals::class;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Lucent\Query\Operator;
class In extends Operator
{
public string $name = "in";
public string $label = "In list";
public string $symbol = "in";
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\In::class;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Lucent\Query\Operator;
class InNum extends Operator
{
public string $name = "innum";
public string $label = "In number list";
public string $symbol = "in";
public array $uis = ["number"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\NotInNum::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class IsNull extends Operator
{
public string $name = "null";
public string $label = "Is Null";
public string $symbol = "is null";
public array $uis = ["*"];
public bool $hasValue = false;
public string $converter = \Lucent\Query\BuilderConverter\IsNull::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class LessThan extends Operator
{
public string $name = "lt";
public string $label = "Less than";
public string $symbol = ">";
public array $uis = ["number", "date", "datetime"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\LessThan::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class LessThanEquals extends Operator
{
public string $name = "lte";
public string $label = "Less than equals";
public string $symbol = ">";
public array $uis = ["number", "date", "datetime"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\LessThanEquals::class;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Lucent\Query\Operator;
class NotEquals extends Operator
{
public string $name = "ne";
public string $label = "Not Equals";
public string $symbol = "is not";
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime", "reference"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\NotEquals::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class NotEqualsFalse extends Operator
{
public string $name = "nefalse";
public string $label = "Not Equals False";
public string $symbol = "is";
public array $uis = ["checkbox"];
public bool $hasValue = false;
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsFalse::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class NotEqualsNumber extends Operator
{
public string $name = "neqnum";
public string $label = "Not Equals Number";
public string $symbol = "is";
public array $uis = ["number"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsNumber::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class NotEqualsTrue extends Operator
{
public string $name = "netrue";
public string $label = "Not Equals True";
public string $symbol = "is";
public array $uis = ["checkbox"];
public bool $hasValue = false;
public string $converter = \Lucent\Query\BuilderConverter\NotEqualsTrue::class;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Query\Operator;
class NotExists extends Operator
{
public string $name = "nexists";
public string $label = "Not Exists";
public string $symbol = "not exists";
public array $uis = ["*"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\NotExists::class;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Lucent\Query\Operator;
class NotIn extends Operator
{
public string $name = "nin";
public string $label = "Not in list";
public string $symbol = "in";
public array $uis = ["id", "text", "textarea", "url", "color", "date", "datetime"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\NotIn::class;
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Lucent\Query\Operator;
class NotInNum extends Operator
{
public string $name = "ninnum";
public string $label = "Not in number list";
public string $symbol = "not in";
public array $uis = ["number"];
public bool $hasValue = true;
public string $converter = \Lucent\Query\BuilderConverter\InNum::class;
}

Some files were not shown because too many files have changed in this diff Show More