This commit is contained in:
Konstantinos Arvanitakis
2026-07-01 18:35:39 +03:00
commit 9f58a36c82
44 changed files with 3848 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
# Activity Log
All domain write operations are logged through `ActivityLogService`, which wraps [Spatie Laravel Activity Log](https://spatie.be/docs/laravel-activitylog).
---
## Channel
All events are written to the `lunar` log channel. Query them with:
```php
use Spatie\Activitylog\Models\Activity;
Activity::on($subject)->get();
Activity::causedBy($staff)->get();
Activity::inLog('lunar')->get();
```
---
## Usage
Inject `ActivityLogService` and call the relevant method after each operation:
```php
use App\Services\Logging\ActivityLogService;
public function __construct(private ActivityLogService $activityLog) {}
// Creation
$this->activityLog->created($model, ['name' => $name, 'type' => $type]);
// Update — pass old state and new state separately
$this->activityLog->updated($model, ['name' => $old], ['name' => $new]);
// Deletion
$this->activityLog->deleted($model, ['name' => $model->name, 'reason' => $reason]);
```
The actor is always resolved from `auth('staff')->user()` at call time — do not pass it explicitly.
---
## Properties Shape
| Method | `properties` key | Contents |
|---|---|---|
| `created` | `attributes` | New state snapshot |
| `updated` | `old` + `attributes` | Previous state and new state |
| `deleted` | `attributes` | Context at time of deletion (name, reason, etc.) |
| `failed` | `attributes` | Context at time of failure (service, error message, etc.) |
---
## What Gets Logged
### Appointments
| Action | Method | Key properties |
|---|---|---|
| Book | `created` | `staff_id`, `date`, `start_time`, `end_time`, `customer` |
| Cancel | `deleted` | `date`, `reason` |
| Reassign | `updated` | old `staff` id → new `staff` id |
| Reschedule | `updated` | old `date`/`start_time`/`end_time` → new values |
### Customers
| Action | Method | Key properties |
|---|---|---|
| Elorus sync success | `updated` | `elorus_id`, `elorus_synced_at` |
| Elorus sync failure | `failed` | `service: elorus`, `error` |
### Availability
| Action | Method | Key properties |
|---|---|---|
| Create schedule | `created` | `name`, `frequency`, `type` |
| Create one-day schedule | `created` | `name`, `type`, `date` |
| Update schedule | `updated` | old name/frequency/type → new values |
| Delete schedule | `deleted` | `name` |
---
## Adding New Events
Follow the existing pattern — log immediately after the operation succeeds, before returning:
```php
$record = $this->doSomething();
$this->activityLog->created($record, ['relevant' => 'context']);
return $record;
```
For updates, capture the old state **before** mutating:
```php
$old = ['name' => $record->name];
$record->update(['name' => $newName]);
$this->activityLog->updated($record, $old, ['name' => $newName]);
```
+1193
View File
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
# OTP Authentication
Staff authentication uses a passwordless OTP flow. No password is stored or required.
---
## Flow
1. Staff enters their email on the login page
2. A 6-digit code is generated, stored on the `staff` record, and emailed
3. Staff enters the code — if valid and not expired, they are logged in
---
## Implementation
### OtpService
`Modules\Core\Services\OtpService` handles generation and validation.
```php
// Generate and email a code
$otpService->generateAndSend(string $email): bool
// Validate a submitted code — returns the Staff model on success, null on failure
$otpService->validate(string $email, string $code): ?Staff
```
Codes expire after **10 minutes**. After a successful validation the code is cleared from the record.
### Database
Two columns on the `lunar_staff` table (added by `2026_05_06_000001_add_otp_to_lunar_staff_table`):
| Column | Type | Purpose |
|---|---|---|
| `otp_code` | string, nullable | The generated code |
| `otp_expires_at` | timestamp, nullable | Expiry time |
### Login Page
`Modules\Core\Filament\Pages\Login` is a Filament `SimplePage` registered as the panel login via `AppServiceProvider`:
```php
LunarPanel::panel(fn (Panel $panel) => $panel->login(Login::class));
```
It has two steps rendered in `core::filament.pages.login`:
- **Step 1** — email form, submits to `requestOtp()`
- **Step 2** — code form, submits to `authenticate()`
After a valid code `canAccessPanel()` is checked before the session is established.
### Email
`Modules\Core\Mail\OtpMail` sends the code using the `core::mail.otp` view.
---
## Disabling 2FA
Lunar's built-in 2FA plugin is disabled in `AppServiceProvider` since the OTP login replaces it:
```php
LunarPanel::disableTwoFactorAuth();
```