211 lines
6.8 KiB
Markdown
211 lines
6.8 KiB
Markdown
# Website Setup
|
||
|
||
This is an opinionated website setup. It's irrelevant to both Lucent and Laravel. It is just the way I like to organize everything.
|
||
|
||
## Summary
|
||
|
||
- Create a Lucent folder with Schemas, Image Filters and Pages
|
||
- Pages are similar to controllers, but will also get used to statically generate the website
|
||
- A Context singleton class that acts as a container for global state
|
||
- A middleware to initiate the Context class and set the application state for the request
|
||
- Some helper functions to make our life easier
|
||
|
||
|
||
## Context Class
|
||
|
||
```php
|
||
<?php namespace App\Lucent;
|
||
|
||
class Context
|
||
{
|
||
public string $website = "";
|
||
public string $title;
|
||
public string $description = "";
|
||
public string $url = "";
|
||
public string $mode = "static";
|
||
public string $locale = "el";
|
||
public array $data = [];
|
||
public array $locales = ["el","en"];
|
||
|
||
protected array $pages = [
|
||
"homepage" => Homepage::class,
|
||
];
|
||
|
||
public function __construct(
|
||
public Application $app,
|
||
public Query $query
|
||
){}
|
||
|
||
public function render(string $pageName, ...$args): string
|
||
{
|
||
$page = $this->app->make($this->pages[$pageName]);
|
||
return $page->render(...$args);
|
||
}
|
||
|
||
public function getTitle()
|
||
{
|
||
return empty($this->title) ? $this->website : $this->title . " | " . $this->website;
|
||
}
|
||
|
||
public function getFullUrl(string $path = "")
|
||
{
|
||
return config("app.url") . $this->generateLocaleUrl($path, $this->locale);
|
||
}
|
||
|
||
public function localeUrl(string $path = ""): string
|
||
{
|
||
return $this->generateLocaleUrl($path, $this->locale);
|
||
}
|
||
|
||
public function switchLocale(string $locale, $path = ""): string
|
||
{
|
||
return $this->generateLocaleUrl($path, $locale);
|
||
}
|
||
|
||
private function generateLocaleUrl(string $path, string $locale): string
|
||
{
|
||
if ($this->mode == "preview") {
|
||
return config("app.url") . "/preview/" . $locale . "/" . trim($path, "/");
|
||
}
|
||
return config("app.url") . "/" . $locale . "/" . trim($path, "/");
|
||
}
|
||
}
|
||
```
|
||
|
||
Add this class to `AppServiceProvider` as a singleton
|
||
|
||
```php
|
||
$this->app->singleton(Context::class);
|
||
```
|
||
|
||
## Context Middleware
|
||
|
||
```php
|
||
<?php namespace App\Http\Middleware;
|
||
|
||
class Context
|
||
{
|
||
public function __construct(public Context $context){}
|
||
|
||
public function handle(Request $request, Closure $next, string $mode = "static"): Response
|
||
{
|
||
$this->context->locale = $request->route("locale");
|
||
App::setLocale($this->meta->locale);
|
||
$this->context->mode = $mode;
|
||
$this->context->loadData();
|
||
return $next($request);
|
||
}
|
||
}
|
||
```
|
||
|
||
Add the middleware inside the HTTP Kernel file.
|
||
|
||
## Configuration
|
||
|
||
After publishing the config (`php artisan vendor:publish --tag=lucent-config`), configure `config/lucent.php` via `.env`:
|
||
|
||
| Key | Env var | Default | Description |
|
||
|---|---|---|---|
|
||
| `env` | `LUCENT_ENV` | `production` | `production` or `development` |
|
||
| `auth` | `LUCENT_AUTH` | `lucent` | Auth driver: `lucent` or `lunar` |
|
||
| `disk` | `LUCENT_DISK` | `public` | Laravel filesystem disk for uploads |
|
||
| `schemas_path` | `LUCENT_SCHEMAS_PATH` | `resources/lucent/schemas` | Where schema JSON files live |
|
||
| `database` | `LUCENT_DB_CONNECTION` | `DB_CONNECTION` | Database connection name |
|
||
| `name` | `LUCENT_NAME` | `Lucent` | CMS display name |
|
||
| `url` | `LUCENT_URL` | `APP_URL` | Base URL of the CMS |
|
||
| `previewTarget` | `LUCENT_PREVIEW_TARGET` | `previewTarget` | Preview route parameter |
|
||
| `commands` | — | `[]` | Artisan commands exposed to admin users (see [Static Generator](Static%20Generator.md)) |
|
||
| `imageFilters` | — | `[]` | Image filter presets applied to uploads (see below) |
|
||
| `canInvite` | — | `["admin"]` | Roles that can invite new users |
|
||
| `canBuild` | — | `["admin"]` | Roles that can trigger static builds |
|
||
| `systemUserId` | — | `""` | User ID used for console-initiated record writes |
|
||
|
||
|
||
## Image Filters
|
||
|
||
Image filters are Intervention Image filter classes applied to every uploaded image, producing additional versions (e.g. a cropped or watermarked variant). Each preset is stored at `templates/{name}/{original-path}` on the configured disk.
|
||
|
||
Define filters in `config/lucent.php`:
|
||
|
||
```php
|
||
"imageFilters" => [
|
||
"hero" => \App\ImageFilters\HeroFilter::class,
|
||
"thumb" => \App\ImageFilters\ThumbFilter::class,
|
||
],
|
||
```
|
||
|
||
A filter class must implement `Intervention\Image\Filters\FilterInterface`:
|
||
|
||
```php
|
||
namespace App\ImageFilters;
|
||
|
||
use Intervention\Image\Filters\FilterInterface;
|
||
use Intervention\Image\Image;
|
||
|
||
class HeroFilter implements FilterInterface
|
||
{
|
||
public function applyFilter(Image $image): Image
|
||
{
|
||
return $image->fit(1200, 600)->encode('webp', 80);
|
||
}
|
||
}
|
||
```
|
||
|
||
Every uploaded image automatically gets a 300×300 WebP thumbnail at `thumbs/{path}` in addition to any configured presets.
|
||
|
||
|
||
## Authentication
|
||
|
||
Lucent uses **email-link (magic link) login** — no passwords. Users receive a time-limited link by email.
|
||
|
||
### Auth modes
|
||
|
||
Set `LUCENT_AUTH` to choose how users are stored:
|
||
|
||
| Value | Description |
|
||
|---|---|
|
||
| `lucent` | Users stored in the `lucent_users` table. Supports roles. |
|
||
| `lunar` | Delegates to Lunar's `lunar_staff` table. Roles not supported. |
|
||
|
||
### Login flow
|
||
|
||
1. User submits their email at `/lucent/login`
|
||
2. A 32-character token is stored against the user with a timestamp
|
||
3. Lucent emails a login link containing the token
|
||
4. User clicks the link → token is validated (must be used within **1 hour**)
|
||
5. Session is established; token is cleared
|
||
|
||
### First-time setup
|
||
|
||
The `/lucent/register` route is only available when **no users exist** in the system. Once the first admin registers, the route returns a redirect to `/lucent/login`. Registration automatically assigns the `admin` role and sends a login link.
|
||
|
||
### Middleware
|
||
|
||
Two middleware aliases are available for your routes:
|
||
|
||
| Alias | Description |
|
||
|---|---|
|
||
| `lucent.auth` | Requires an active Lucent session |
|
||
| `lucent.guest` | Redirects authenticated users away |
|
||
|
||
### Roles
|
||
|
||
Roles are defined in your channel config. Only roles listed there are valid — anything else is silently stripped on assignment. The `canInvite` and `canBuild` config keys control which roles can invite users and trigger builds respectively.
|
||
|
||
On console commands and non-`/lucent` routes, `currentUserId()` returns `config("lucent.systemUserId")` instead of the session user.
|
||
|
||
|
||
## Database Tables
|
||
|
||
All Lucent-managed tables use the `lucent_` prefix:
|
||
|
||
| Table | Description |
|
||
|---|---|
|
||
| `lucent_records` | All collection and file schema records |
|
||
| `lucent_files` | Uploaded file metadata |
|
||
| `lucent_edges` | Relationships between records |
|
||
| `lucent_revisions` | Record revision history |
|
||
| `lucent_users` | Users (when using `lucent` auth mode) |
|
||
| `lucent_command_logs` | Background command execution logs |
|
||
|
||
These are created automatically by running `php artisan lucent:setup`. |