# Libra Law Firm - Architecture Document
**Version:** 1.1
**Date:** December 21, 2025
**Status:** Approved
**Domain:** libra.ps
---
## Change Log
| Date | Version | Description | Author |
|------|---------|-------------|--------|
| Dec 21, 2025 | 1.0 | Initial architecture document | Winston (Architect) |
| Dec 21, 2025 | 1.1 | Added CI/CD, rollback procedures, monitoring, timezone handling, alternatives analysis | Winston (Architect) |
---
## Table of Contents
1. [Introduction](#1-introduction)
2. [High Level Architecture](#2-high-level-architecture)
3. [Technology Stack](#3-technology-stack)
4. [Data Models](#4-data-models)
5. [Database Schema](#5-database-schema)
6. [Application Structure](#6-application-structure)
7. [Authentication & Authorization](#7-authentication--authorization)
8. [Core Workflows](#8-core-workflows)
9. [Routing Structure](#9-routing-structure)
10. [Email System](#10-email-system)
11. [Background Jobs & Scheduling](#11-background-jobs--scheduling)
12. [Localization & Timezone](#12-localization--timezone)
13. [Security](#13-security)
14. [Testing Strategy](#14-testing-strategy)
15. [Performance & Optimization](#15-performance--optimization)
16. [Deployment & CI/CD](#16-deployment--cicd)
17. [Monitoring & Alerting](#17-monitoring--alerting)
18. [Error Handling & Graceful Degradation](#18-error-handling--graceful-degradation)
19. [Rollback & Recovery Procedures](#19-rollback--recovery-procedures)
20. [Coding Standards](#20-coding-standards)
21. [Technology Alternatives Considered](#21-technology-alternatives-considered)
22. [Appendices](#22-appendices)
---
## 1. Introduction
### 1.1 Purpose
This document outlines the complete architecture for Libra Law Firm's web platform. It serves as the single source of truth for development, ensuring consistency and guiding AI-driven implementation.
### 1.2 Project Overview
Libra Law Firm is a bilingual (Arabic/English) Laravel application serving as both a public-facing website and internal management tool for:
- Client consultation booking and management
- Case timeline tracking
- Legal content publishing
- Administrative operations
### 1.3 Key Constraints
| Constraint | Details |
|------------|---------|
| Single Admin | One lawyer manages all operations |
| No Online Payments | All payments handled offline |
| Client-Managed Infrastructure | Rocky Linux 9 server |
| No Self-Registration | Admin creates all client accounts |
| Bilingual Requirement | Arabic (RTL) primary, English (LTR) secondary |
### 1.4 Project Context
**Existing Foundation:**
- Laravel 12.43.1 with Fortify authentication
- Livewire 3.7.3 + Volt 1.10.1 for reactive components
- Flux UI Free 2.10.2 component library
- Tailwind CSS 4.1.11
- Pest 4.2.0 testing framework
---
## 2. High Level Architecture
### 2.1 Technical Summary
Libra is a **server-rendered Laravel monolith** with Livewire for reactive UI. The architecture follows Laravel's MVC pattern enhanced with:
- **Livewire 3 + Volt** for interactive components without JavaScript complexity
- **Fortify** for headless authentication with custom Volt views
- **Eloquent ORM** for database operations
- **Blade + Flux UI** for consistent, accessible component design
- **Queue workers** for background email processing and scheduled reminders
This architecture prioritizes **simplicity and maintainability** over distributed complexity.
### 2.2 System Context Diagram
```mermaid
graph TB
subgraph "Users"
Admin[Admin/Lawyer]
Client[Clients]
Public[Public Visitors]
end
subgraph "Libra Platform"
App[Laravel Application
libra.ps]
end
subgraph "External Services"
SMTP[SMTP Server
no-reply@libra.ps]
end
subgraph "Infrastructure"
Server[Rocky Linux 9
Client-Managed]
end
Admin -->|Manage users, bookings,
timelines, posts| App
Client -->|Book consultations,
view timelines| App
Public -->|View posts,
firm info| App
App -->|Send emails| SMTP
App -->|Deployed on| Server
```
### 2.3 Container Diagram
```mermaid
graph TB
subgraph "Client Devices"
Browser[Web Browser]
Mobile[Mobile Browser]
end
subgraph "Web Server - Nginx"
Static[Static Assets
CSS/JS/Images]
PHP[PHP-FPM 8.4]
end
subgraph "Laravel Application"
Router[Route Handler]
MW[Middleware Stack
Auth, Locale, CSRF]
subgraph "Presentation Layer"
Volt[Volt Components]
Flux[Flux UI Components]
Blade[Blade Templates]
end
subgraph "Business Logic Layer"
Actions[Action Classes]
Fortify[Fortify Auth]
Policies[Authorization Policies]
end
subgraph "Data Access Layer"
Models[Eloquent Models]
Observers[Model Observers]
end
end
subgraph "Background Processing"
Queue[Queue Worker
Supervisor]
Scheduler[Task Scheduler
Cron]
end
subgraph "Data Storage"
MariaDB[(MariaDB
Production)]
SQLite[(SQLite
Development)]
FileStorage[Local Storage
Exports/Logs]
end
subgraph "External"
SMTP[SMTP Server]
end
Browser --> Static
Browser --> PHP
Mobile --> Static
Mobile --> PHP
PHP --> Router
Router --> MW
MW --> Volt
MW --> Blade
Volt --> Flux
Volt --> Actions
Actions --> Fortify
Actions --> Policies
Actions --> Models
Models --> Observers
Models --> MariaDB
Models --> SQLite
Queue --> Actions
Queue --> SMTP
Scheduler --> Queue
Actions --> FileStorage
```
### 2.4 Architectural Patterns
| Pattern | Description | Rationale |
|---------|-------------|-----------|
| **MVC + Livewire** | Laravel MVC with Livewire reactive components | Standard Laravel pattern; eliminates need for separate SPA |
| **Action Classes** | Single-purpose classes for business logic | Keeps controllers thin; reusable; testable |
| **Form Objects** | Livewire form objects for validation | Encapsulates validation; reusable forms |
| **Observer Pattern** | Eloquent observers for model events | Automatic audit logging, notification triggers |
| **Queue-based Processing** | Background jobs for emails and heavy operations | Non-blocking UX; reliable delivery |
| **Policy-based Authorization** | Laravel Policies for access control | Clean separation of authorization logic |
| **Repository Pattern** | Optional for complex queries | Only if query complexity warrants |
---
## 3. Technology Stack
### 3.1 Core Technologies
| Category | Technology | Version | Purpose | Rationale |
|----------|------------|---------|---------|-----------|
| **Runtime** | PHP | 8.4.x | Server-side language | Latest stable; required by Laravel 12 |
| **Framework** | Laravel | 12.x | Application framework | Industry standard; excellent ecosystem |
| **Reactive UI** | Livewire | 3.7.x | Interactive components | No JS build complexity; server-state |
| **Components** | Volt | 1.10.x | Single-file components | Cleaner organization; class-based |
| **UI Library** | Flux UI Free | 2.10.x | Pre-built components | Consistent design; accessibility |
| **CSS** | Tailwind CSS | 4.x | Utility-first styling | Rapid development; RTL support |
| **Auth** | Laravel Fortify | 1.33.x | Headless auth | Flexible; 2FA support |
| **Database (Prod)** | MariaDB | 10.11+ | Production database | MySQL-compatible; performant |
| **Database (Dev)** | SQLite | Latest | Development database | Zero config; fast tests |
| **Testing** | Pest | 4.x | Testing framework | Elegant syntax; Laravel integration |
| **Code Style** | Laravel Pint | 1.x | Code formatting | Consistent style |
| **Queue** | Database Driver | - | Job processing | Simple; no Redis dependency |
### 3.2 Frontend Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| Alpine.js | (via Livewire) | Minimal JS interactivity |
| Vite | 6.x | Asset bundling |
| Google Fonts | - | Cairo (Arabic), Montserrat (English) |
| Heroicons | (via Flux) | Icon library |
### 3.3 Backend Dependencies (To Add)
| Package | Version | Purpose |
|---------|---------|---------|
| `barryvdh/laravel-dompdf` | ^3.0 | PDF export generation |
| `league/csv` | ^9.0 | CSV export generation |
| `spatie/icalendar-generator` | ^2.0 | .ics calendar file generation |
### 3.4 Development Dependencies
| Package | Purpose |
|---------|---------|
| Laravel Sail | Docker development environment (optional) |
| Laravel Telescope | Debug assistant (dev only) |
| Laravel Pint | Code formatting |
---
## 4. Data Models
### 4.1 Entity Relationship Diagram
```mermaid
erDiagram
users ||--o{ consultations : "has many"
users ||--o{ timelines : "has many"
users ||--o{ notifications : "has many"
timelines ||--o{ timeline_updates : "has many"
users ||--o{ admin_logs : "performed by"
users {
bigint id PK
enum user_type "individual|company|admin"
string full_name
string national_id "nullable, encrypted"
string company_name "nullable"
string company_cert_number "nullable"
string contact_person_name "nullable"
string contact_person_id "nullable"
string email UK
string phone
string password "hashed"
enum status "active|deactivated"
string preferred_language "ar|en"
timestamp email_verified_at
text two_factor_secret "nullable"
text two_factor_recovery_codes "nullable"
timestamp two_factor_confirmed_at
timestamps created_updated
}
consultations {
bigint id PK
bigint user_id FK
date booking_date
time booking_time
text problem_summary
enum consultation_type "free|paid"
decimal payment_amount "nullable"
enum payment_status "pending|received|na"
enum status "pending|approved|rejected|completed|no_show|cancelled"
text admin_notes "nullable"
timestamps created_updated
}
timelines {
bigint id PK
bigint user_id FK
string case_name
string case_reference "nullable"
enum status "active|archived"
timestamps created_updated
}
timeline_updates {
bigint id PK
bigint timeline_id FK
bigint admin_id FK
text update_text
timestamps created_updated
}
posts {
bigint id PK
json title "ar and en"
json body "ar and en"
enum status "draft|published"
timestamp published_at "nullable"
timestamps created_updated
}
working_hours {
bigint id PK
tinyint day_of_week "0-6 Sunday-Saturday"
time start_time
time end_time
boolean is_active
}
blocked_times {
bigint id PK
date block_date
time start_time "nullable for full day"
time end_time "nullable for full day"
string reason "nullable"
timestamps created_updated
}
notifications {
bigint id PK
bigint user_id FK
string type
json data
timestamp read_at "nullable"
timestamp sent_at "nullable"
timestamps created_updated
}
admin_logs {
bigint id PK
bigint admin_id FK
string action
string target_type
bigint target_id "nullable"
json old_values "nullable"
json new_values "nullable"
string ip_address
timestamp created_at
}
settings {
bigint id PK
string key UK
json value
timestamps created_updated
}
```
### 4.2 Model Definitions
#### User Model
```php
// app/Models/User.php
class User extends Authenticatable
{
use HasFactory, Notifiable, TwoFactorAuthenticatable;
protected $fillable = [
'user_type',
'full_name',
'national_id',
'company_name',
'company_cert_number',
'contact_person_name',
'contact_person_id',
'email',
'phone',
'password',
'status',
'preferred_language',
];
protected $hidden = [
'password',
'remember_token',
'two_factor_secret',
'two_factor_recovery_codes',
];
protected function casts(): array
{
return [
'user_type' => UserType::class,
'status' => UserStatus::class,
'email_verified_at' => 'datetime',
'two_factor_confirmed_at' => 'datetime',
'password' => 'hashed',
];
}
// Relationships
public function consultations(): HasMany;
public function timelines(): HasMany;
public function notifications(): HasMany;
public function adminLogs(): HasMany;
// Scopes
public function scopeActive(Builder $query): Builder;
public function scopeClients(Builder $query): Builder;
public function scopeIndividuals(Builder $query): Builder;
public function scopeCompanies(Builder $query): Builder;
// Helpers
public function isAdmin(): bool;
public function isClient(): bool;
public function isIndividual(): bool;
public function isCompany(): bool;
public function canBookOnDate(Carbon $date): bool;
public function hasBookingOnDate(Carbon $date): bool;
}
```
#### Consultation Model
```php
// app/Models/Consultation.php
class Consultation extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'booking_date',
'booking_time',
'problem_summary',
'consultation_type',
'payment_amount',
'payment_status',
'status',
'admin_notes',
];
protected function casts(): array
{
return [
'booking_date' => 'date',
'booking_time' => 'datetime:H:i',
'consultation_type' => ConsultationType::class,
'payment_status' => PaymentStatus::class,
'status' => ConsultationStatus::class,
'payment_amount' => 'decimal:2',
];
}
// Relationships
public function user(): BelongsTo;
// Scopes
public function scopePending(Builder $query): Builder;
public function scopeApproved(Builder $query): Builder;
public function scopeUpcoming(Builder $query): Builder;
public function scopeForDate(Builder $query, Carbon $date): Builder;
public function scopeForDateRange(Builder $query, Carbon $start, Carbon $end): Builder;
// Accessors
public function getEndTimeAttribute(): Carbon;
public function getStartDateTimeAttribute(): Carbon;
public function getEndDateTimeAttribute(): Carbon;
// Helpers
public function isPaid(): bool;
public function isPending(): bool;
public function canBeApproved(): bool;
public function canBeCancelled(): bool;
}
```
#### Timeline Model
```php
// app/Models/Timeline.php
class Timeline extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'case_name',
'case_reference',
'status',
];
protected function casts(): array
{
return [
'status' => TimelineStatus::class,
];
}
// Relationships
public function user(): BelongsTo;
public function updates(): HasMany;
public function latestUpdate(): HasOne;
// Scopes
public function scopeActive(Builder $query): Builder;
public function scopeArchived(Builder $query): Builder;
public function scopeForUser(Builder $query, User $user): Builder;
}
```
#### Post Model
```php
// app/Models/Post.php
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'body',
'status',
'published_at',
];
protected function casts(): array
{
return [
'title' => 'array',
'body' => 'array',
'status' => PostStatus::class,
'published_at' => 'datetime',
];
}
// Accessors for localized content
public function getTitleLocalizedAttribute(): string;
public function getBodyLocalizedAttribute(): string;
// Scopes
public function scopePublished(Builder $query): Builder;
public function scopeDraft(Builder $query): Builder;
// Helpers
public function isPublished(): bool;
}
```
### 4.3 Enums
```php
// app/Enums/UserType.php
enum UserType: string
{
case Individual = 'individual';
case Company = 'company';
case Admin = 'admin';
public function label(): string
{
return match($this) {
self::Individual => __('enums.user_type.individual'),
self::Company => __('enums.user_type.company'),
self::Admin => __('enums.user_type.admin'),
};
}
}
// app/Enums/UserStatus.php
enum UserStatus: string
{
case Active = 'active';
case Deactivated = 'deactivated';
}
// app/Enums/ConsultationType.php
enum ConsultationType: string
{
case Free = 'free';
case Paid = 'paid';
}
// app/Enums/ConsultationStatus.php
enum ConsultationStatus: string
{
case Pending = 'pending';
case Approved = 'approved';
case Rejected = 'rejected';
case Completed = 'completed';
case NoShow = 'no_show';
case Cancelled = 'cancelled';
public function color(): string
{
return match($this) {
self::Pending => 'yellow',
self::Approved => 'blue',
self::Rejected => 'red',
self::Completed => 'green',
self::NoShow => 'gray',
self::Cancelled => 'red',
};
}
}
// app/Enums/PaymentStatus.php
enum PaymentStatus: string
{
case Pending = 'pending';
case Received = 'received';
case NotApplicable = 'na';
}
// app/Enums/TimelineStatus.php
enum TimelineStatus: string
{
case Active = 'active';
case Archived = 'archived';
}
// app/Enums/PostStatus.php
enum PostStatus: string
{
case Draft = 'draft';
case Published = 'published';
}
```
---
## 5. Database Schema
### 5.1 Migration Order
1. `0001_01_01_000000_create_users_table.php` (modify existing)
2. `0001_01_01_000001_create_cache_table.php` (existing)
3. `0001_01_01_000002_create_jobs_table.php` (existing)
4. `2025_01_01_000001_add_profile_fields_to_users_table.php`
5. `2025_01_01_000002_create_consultations_table.php`
6. `2025_01_01_000003_create_timelines_table.php`
7. `2025_01_01_000004_create_timeline_updates_table.php`
8. `2025_01_01_000005_create_posts_table.php`
9. `2025_01_01_000006_create_working_hours_table.php`
10. `2025_01_01_000007_create_blocked_times_table.php`
11. `2025_01_01_000008_create_notifications_table.php`
12. `2025_01_01_000009_create_admin_logs_table.php`
13. `2025_01_01_000010_create_settings_table.php`
### 5.2 Key Migration Examples
#### Users Table Extension
```php
// database/migrations/2025_01_01_000001_add_profile_fields_to_users_table.php
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('user_type')->default('individual')->after('id');
$table->string('full_name')->nullable()->after('name');
$table->string('national_id')->nullable()->after('full_name');
$table->string('company_name')->nullable()->after('national_id');
$table->string('company_cert_number')->nullable()->after('company_name');
$table->string('contact_person_name')->nullable()->after('company_cert_number');
$table->string('contact_person_id')->nullable()->after('contact_person_name');
$table->string('phone', 20)->nullable()->after('email');
$table->string('status')->default('active')->after('password');
$table->string('preferred_language', 2)->default('ar')->after('status');
$table->index('user_type');
$table->index('status');
$table->index(['user_type', 'status']);
});
}
```
#### Consultations Table
```php
// database/migrations/2025_01_01_000002_create_consultations_table.php
public function up(): void
{
Schema::create('consultations', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->date('booking_date');
$table->time('booking_time');
$table->text('problem_summary');
$table->string('consultation_type')->default('free');
$table->decimal('payment_amount', 10, 2)->nullable();
$table->string('payment_status')->default('na');
$table->string('status')->default('pending');
$table->text('admin_notes')->nullable();
$table->timestamps();
$table->index('booking_date');
$table->index('status');
$table->index(['booking_date', 'booking_time']);
$table->unique(['user_id', 'booking_date']); // One booking per user per day
});
}
```
#### Working Hours Table
```php
// database/migrations/2025_01_01_000006_create_working_hours_table.php
public function up(): void
{
Schema::create('working_hours', function (Blueprint $table) {
$table->id();
$table->tinyInteger('day_of_week')->unsigned(); // 0=Sunday, 6=Saturday
$table->time('start_time');
$table->time('end_time');
$table->boolean('is_active')->default(true);
$table->unique('day_of_week');
});
}
```
### 5.3 Indexes Strategy
| Table | Index | Columns | Purpose |
|-------|-------|---------|---------|
| users | user_type_status | user_type, status | Filter clients |
| consultations | booking_date | booking_date | Calendar queries |
| consultations | status | status | Filter by status |
| consultations | user_date_unique | user_id, booking_date | Enforce 1/day limit |
| timelines | user_status | user_id, status | Client timeline list |
| posts | status_published | status, published_at | Published posts list |
| admin_logs | target | target_type, target_id | Audit trail lookup |
---
## 6. Application Structure
### 6.1 Directory Structure
```
libra/
├── app/
│ ├── Actions/ # Business logic (single-purpose)
│ │ ├── Fortify/ # Auth actions (existing)
│ │ │ ├── CreateNewUser.php
│ │ │ ├── ResetUserPassword.php
│ │ │ └── UpdateUserPassword.php
│ │ ├── Consultation/
│ │ │ ├── CreateConsultationAction.php
│ │ │ ├── ApproveConsultationAction.php
│ │ │ ├── RejectConsultationAction.php
│ │ │ ├── CompleteConsultationAction.php
│ │ │ └── CancelConsultationAction.php
│ │ ├── Timeline/
│ │ │ ├── CreateTimelineAction.php
│ │ │ ├── AddTimelineUpdateAction.php
│ │ │ └── ArchiveTimelineAction.php
│ │ ├── User/
│ │ │ ├── CreateClientAction.php
│ │ │ ├── UpdateClientAction.php
│ │ │ ├── DeactivateClientAction.php
│ │ │ ├── ReactivateClientAction.php
│ │ │ ├── DeleteClientAction.php
│ │ │ └── ConvertClientTypeAction.php
│ │ ├── Post/
│ │ │ ├── CreatePostAction.php
│ │ │ ├── UpdatePostAction.php
│ │ │ ├── PublishPostAction.php
│ │ │ └── DeletePostAction.php
│ │ └── Export/
│ │ ├── ExportUsersAction.php
│ │ ├── ExportConsultationsAction.php
│ │ └── GenerateMonthlyReportAction.php
│ ├── Enums/ # PHP 8.1+ enums
│ │ ├── UserType.php
│ │ ├── UserStatus.php
│ │ ├── ConsultationType.php
│ │ ├── ConsultationStatus.php
│ │ ├── PaymentStatus.php
│ │ ├── TimelineStatus.php
│ │ └── PostStatus.php
│ ├── Http/
│ │ ├── Controllers/ # Minimal controllers
│ │ │ ├── LanguageController.php
│ │ │ └── CalendarDownloadController.php
│ │ └── Middleware/
│ │ ├── SetLocale.php
│ │ ├── EnsureUserIsAdmin.php
│ │ └── EnsureUserIsActive.php
│ ├── Jobs/ # Queue jobs
│ │ ├── SendWelcomeEmail.php
│ │ ├── SendBookingNotification.php
│ │ ├── SendConsultationReminder.php
│ │ ├── SendTimelineUpdateNotification.php
│ │ └── GenerateExportFile.php
│ ├── Livewire/ # Full-page Livewire components (if any)
│ │ └── Forms/ # Livewire form objects
│ │ ├── ConsultationForm.php
│ │ ├── ClientForm.php
│ │ ├── TimelineForm.php
│ │ └── PostForm.php
│ ├── Mail/ # Mailable classes
│ │ ├── WelcomeMail.php
│ │ ├── BookingSubmittedMail.php
│ │ ├── BookingApprovedMail.php
│ │ ├── BookingRejectedMail.php
│ │ ├── ConsultationReminderMail.php
│ │ ├── TimelineUpdatedMail.php
│ │ └── NewBookingRequestMail.php
│ ├── Models/ # Eloquent models
│ │ ├── User.php
│ │ ├── Consultation.php
│ │ ├── Timeline.php
│ │ ├── TimelineUpdate.php
│ │ ├── Post.php
│ │ ├── WorkingHour.php
│ │ ├── BlockedTime.php
│ │ ├── Notification.php
│ │ ├── AdminLog.php
│ │ └── Setting.php
│ ├── Observers/ # Model observers
│ │ ├── ConsultationObserver.php
│ │ ├── TimelineUpdateObserver.php
│ │ └── UserObserver.php
│ ├── Policies/ # Authorization policies
│ │ ├── ConsultationPolicy.php
│ │ ├── TimelinePolicy.php
│ │ ├── PostPolicy.php
│ │ └── UserPolicy.php
│ ├── Providers/
│ │ ├── AppServiceProvider.php
│ │ └── FortifyServiceProvider.php
│ └── Services/ # Cross-cutting services
│ ├── AvailabilityService.php
│ ├── CalendarService.php
│ └── ExportService.php
├── config/
│ └── libra.php # App-specific config
├── database/
│ ├── factories/
│ │ ├── UserFactory.php
│ │ ├── ConsultationFactory.php
│ │ ├── TimelineFactory.php
│ │ ├── TimelineUpdateFactory.php
│ │ └── PostFactory.php
│ ├── migrations/
│ └── seeders/
│ ├── DatabaseSeeder.php
│ ├── AdminSeeder.php
│ ├── WorkingHoursSeeder.php
│ └── DemoDataSeeder.php
├── docs/
│ ├── architecture.md # This document
│ ├── prd.md # Product requirements
│ ├── epics/ # Epic definitions
│ └── stories/ # User stories
├── resources/
│ ├── views/
│ │ ├── components/ # Blade components
│ │ │ ├── layouts/
│ │ │ │ ├── app.blade.php # Main app layout
│ │ │ │ ├── guest.blade.php # Guest/public layout
│ │ │ │ └── admin.blade.php # Admin layout
│ │ │ └── ui/ # Reusable UI components
│ │ │ ├── card.blade.php
│ │ │ ├── stat-card.blade.php
│ │ │ ├── status-badge.blade.php
│ │ │ └── language-switcher.blade.php
│ │ ├── emails/ # Email templates
│ │ │ ├── welcome.blade.php
│ │ │ ├── booking-submitted.blade.php
│ │ │ ├── booking-approved.blade.php
│ │ │ ├── booking-rejected.blade.php
│ │ │ ├── consultation-reminder.blade.php
│ │ │ ├── timeline-updated.blade.php
│ │ │ └── new-booking-request.blade.php
│ │ ├── livewire/ # Volt single-file components
│ │ │ ├── admin/ # Admin area
│ │ │ │ ├── dashboard.blade.php
│ │ │ │ ├── users/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ ├── create.blade.php
│ │ │ │ │ └── edit.blade.php
│ │ │ │ ├── consultations/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ ├── pending.blade.php
│ │ │ │ │ ├── calendar.blade.php
│ │ │ │ │ └── show.blade.php
│ │ │ │ ├── timelines/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ ├── create.blade.php
│ │ │ │ │ └── show.blade.php
│ │ │ │ ├── posts/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ ├── create.blade.php
│ │ │ │ │ └── edit.blade.php
│ │ │ │ ├── settings/
│ │ │ │ │ ├── working-hours.blade.php
│ │ │ │ │ ├── blocked-times.blade.php
│ │ │ │ │ └── content-pages.blade.php
│ │ │ │ └── reports/
│ │ │ │ └── index.blade.php
│ │ │ ├── client/ # Client area
│ │ │ │ ├── dashboard.blade.php
│ │ │ │ ├── consultations/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ └── book.blade.php
│ │ │ │ ├── timelines/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ └── show.blade.php
│ │ │ │ └── profile.blade.php
│ │ │ ├── pages/ # Public pages
│ │ │ │ ├── home.blade.php
│ │ │ │ ├── posts/
│ │ │ │ │ ├── index.blade.php
│ │ │ │ │ └── show.blade.php
│ │ │ │ ├── terms.blade.php
│ │ │ │ └── privacy.blade.php
│ │ │ ├── auth/ # Auth pages (existing)
│ │ │ └── settings/ # User settings (existing)
│ │ ├── pdf/ # PDF templates
│ │ │ ├── users-export.blade.php
│ │ │ ├── consultations-export.blade.php
│ │ │ └── monthly-report.blade.php
│ │ └── errors/ # Error pages
│ │ ├── 404.blade.php
│ │ ├── 403.blade.php
│ │ └── 500.blade.php
│ ├── css/
│ │ └── app.css # Tailwind entry point
│ ├── js/
│ │ └── app.js # Minimal JS
│ └── lang/ # Translations
│ ├── ar/
│ │ ├── auth.php
│ │ ├── validation.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ ├── messages.php
│ │ ├── models.php
│ │ ├── enums.php
│ │ └── emails.php
│ └── en/
│ └── ... (same structure)
├── routes/
│ ├── web.php # Web routes
│ └── console.php # Scheduled commands
├── tests/
│ ├── Feature/
│ │ ├── Admin/
│ │ │ ├── DashboardTest.php
│ │ │ ├── UserManagementTest.php
│ │ │ ├── ConsultationManagementTest.php
│ │ │ ├── TimelineManagementTest.php
│ │ │ ├── PostManagementTest.php
│ │ │ └── SettingsTest.php
│ │ ├── Client/
│ │ │ ├── DashboardTest.php
│ │ │ ├── BookingTest.php
│ │ │ ├── TimelineViewTest.php
│ │ │ └── ProfileTest.php
│ │ ├── Public/
│ │ │ ├── HomePageTest.php
│ │ │ ├── PostsTest.php
│ │ │ └── LanguageSwitchTest.php
│ │ └── Auth/
│ │ └── ... (existing tests)
│ └── Unit/
│ ├── Actions/
│ ├── Models/
│ ├── Services/
│ └── Enums/
├── storage/
│ └── app/
│ └── exports/ # Generated export files
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI
└── public/
└── build/ # Compiled assets
```
---
## 7. Authentication & Authorization
### 7.1 Authentication (Fortify)
**Current Configuration:**
- Email/password login
- Email verification
- Two-factor authentication (TOTP)
- Password reset
**Modifications Required:**
1. **Disable public registration** - Admin creates all users
2. **Custom login redirect** - Admin → `/admin/dashboard`, Client → `/client/dashboard`
3. **Admin-triggered password reset** - No public reset
```php
// config/fortify.php
'features' => [
// Features::registration(), // DISABLED
Features::resetPasswords(),
Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]),
],
```
### 7.2 Authorization (Policies)
```php
// app/Policies/ConsultationPolicy.php
class ConsultationPolicy
{
public function viewAny(User $user): bool
{
return true; // All authenticated users can see their own
}
public function view(User $user, Consultation $consultation): bool
{
return $user->isAdmin() || $consultation->user_id === $user->id;
}
public function create(User $user): bool
{
return $user->isClient();
}
public function update(User $user, Consultation $consultation): bool
{
return $user->isAdmin();
}
public function delete(User $user, Consultation $consultation): bool
{
return $user->isAdmin();
}
}
// app/Policies/TimelinePolicy.php
class TimelinePolicy
{
public function viewAny(User $user): bool
{
return true;
}
public function view(User $user, Timeline $timeline): bool
{
return $user->isAdmin() || $timeline->user_id === $user->id;
}
public function create(User $user): bool
{
return $user->isAdmin();
}
public function update(User $user, Timeline $timeline): bool
{
return $user->isAdmin();
}
public function addUpdate(User $user, Timeline $timeline): bool
{
return $user->isAdmin();
}
}
// app/Policies/UserPolicy.php
class UserPolicy
{
public function viewAny(User $user): bool
{
return $user->isAdmin();
}
public function view(User $user, User $model): bool
{
return $user->isAdmin() || $user->id === $model->id;
}
public function create(User $user): bool
{
return $user->isAdmin();
}
public function update(User $user, User $model): bool
{
return $user->isAdmin();
}
public function delete(User $user, User $model): bool
{
return $user->isAdmin() && !$model->isAdmin();
}
}
```
### 7.3 Middleware
```php
// app/Http/Middleware/SetLocale.php
class SetLocale
{
public function handle(Request $request, Closure $next): Response
{
$locale = session('locale')
?? $request->user()?->preferred_language
?? config('app.locale');
if (!in_array($locale, ['ar', 'en'])) {
$locale = 'ar';
}
app()->setLocale($locale);
return $next($request);
}
}
// app/Http/Middleware/EnsureUserIsAdmin.php
class EnsureUserIsAdmin
{
public function handle(Request $request, Closure $next): Response
{
if (!$request->user()?->isAdmin()) {
abort(403, __('messages.unauthorized'));
}
return $next($request);
}
}
// app/Http/Middleware/EnsureUserIsActive.php
class EnsureUserIsActive
{
public function handle(Request $request, Closure $next): Response
{
if ($request->user()?->status === UserStatus::Deactivated) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login')
->with('error', __('messages.account_deactivated'));
}
return $next($request);
}
}
```
### 7.4 Middleware Registration
```php
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\SetLocale::class,
]);
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
'active' => \App\Http\Middleware\EnsureUserIsActive::class,
]);
$middleware->validateCsrfTokens(except: [
// Add any exceptions here
]);
})
```
---
## 8. Core Workflows
### 8.1 Consultation Booking Flow
```mermaid
sequenceDiagram
participant C as Client
participant V as Volt Component
participant A as Action
participant S as AvailabilityService
participant M as Model
participant Q as Queue
participant E as Email (Admin)
C->>V: Open booking page
V->>S: getAvailableSlots(date)
S->>M: Query working_hours, blocked_times, consultations
S-->>V: Available time slots
V-->>C: Display calendar
C->>V: Select date/time, enter summary
V->>V: Validate (date future, slot available, no existing booking)
alt Validation fails
V-->>C: Show error message
else Validation passes
V->>A: CreateConsultationAction
A->>M: Check user has no booking on date
A->>M: Create consultation (status: pending)
A->>M: Log admin action
M-->>A: Consultation created
A->>Q: Dispatch SendBookingNotification (to admin)
Q->>E: Email admin at no-reply@libra.ps
A-->>V: Success
V-->>C: Show confirmation message
end
```
### 8.2 Admin Consultation Approval Flow
```mermaid
sequenceDiagram
participant Ad as Admin
participant V as Volt Component
participant A as Action
participant M as Model
participant Q as Queue
participant E as Email (Client)
Ad->>V: View pending consultation
Ad->>V: Set type (free/paid), amount if paid
Ad->>V: Click Approve
V->>A: ApproveConsultationAction
A->>M: Update consultation (status: approved, type, amount)
A->>M: Log admin action
M-->>A: Updated
A->>Q: Dispatch SendBookingApprovedMail (with .ics attachment)
A->>Q: Schedule SendConsultationReminder (24h before)
A->>Q: Schedule SendConsultationReminder (2h before)
Q->>E: Send approval email to client
A-->>V: Success
V-->>Ad: Refresh list, show success toast
```
### 8.3 Timeline Update Flow
```mermaid
sequenceDiagram
participant Ad as Admin
participant V as Volt Component
participant A as Action
participant M as Model
participant O as Observer
participant Q as Queue
participant E as Email
Ad->>V: Navigate to timeline
Ad->>V: Enter update text
Ad->>V: Submit
V->>A: AddTimelineUpdateAction
A->>M: Create TimelineUpdate
M->>O: TimelineUpdateObserver::created
O->>Q: Dispatch SendTimelineUpdateNotification
A->>M: Log admin action
A-->>V: Success
Q->>E: Send notification to client
V-->>Ad: Refresh timeline, show new update
```
### 8.4 User Creation Flow
```mermaid
sequenceDiagram
participant Ad as Admin
participant V as Volt Component
participant A as Action
participant M as Model
participant Q as Queue
participant E as Email
Ad->>V: Fill user form (type, name, email, etc.)
Ad->>V: Set password
Ad->>V: Submit
V->>V: Validate form
V->>A: CreateClientAction
A->>M: Create User
A->>M: Log admin action
M-->>A: User created
A->>Q: Dispatch SendWelcomeEmail (with credentials)
Q->>E: Send welcome email to new user
A-->>V: Success
V-->>Ad: Redirect to user list
```
---
## 9. Routing Structure
### 9.1 Route Definitions
```php
// routes/web.php
use App\Http\Controllers\LanguageController;
use App\Http\Controllers\CalendarDownloadController;
use Illuminate\Support\Facades\Route;
use Livewire\Volt\Volt;
/*
|--------------------------------------------------------------------------
| Public Routes
|--------------------------------------------------------------------------
*/
// Home
Volt::route('/', 'pages.home')->name('home');
// Posts
Volt::route('/posts', 'pages.posts.index')->name('posts.index');
Volt::route('/posts/{post}', 'pages.posts.show')->name('posts.show');
// Legal pages
Volt::route('/terms', 'pages.terms')->name('terms');
Volt::route('/privacy', 'pages.privacy')->name('privacy');
// Language switch
Route::get('/language/{locale}', LanguageController::class)
->name('language.switch')
->where('locale', 'ar|en');
/*
|--------------------------------------------------------------------------
| Client Routes (Authenticated Non-Admin)
|--------------------------------------------------------------------------
*/
Route::middleware(['auth', 'verified', 'active'])->prefix('client')->name('client.')->group(function () {
Volt::route('/dashboard', 'client.dashboard')->name('dashboard');
// Consultations
Volt::route('/consultations', 'client.consultations.index')->name('consultations.index');
Volt::route('/consultations/book', 'client.consultations.book')->name('consultations.book');
Route::get('/consultations/{consultation}/calendar', CalendarDownloadController::class)
->name('consultations.calendar');
// Timelines
Volt::route('/timelines', 'client.timelines.index')->name('timelines.index');
Volt::route('/timelines/{timeline}', 'client.timelines.show')->name('timelines.show');
// Profile (view only)
Volt::route('/profile', 'client.profile')->name('profile');
});
/*
|--------------------------------------------------------------------------
| Admin Routes
|--------------------------------------------------------------------------
*/
Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function () {
Volt::route('/dashboard', 'admin.dashboard')->name('dashboard');
// Users
Volt::route('/users', 'admin.users.index')->name('users.index');
Volt::route('/users/create', 'admin.users.create')->name('users.create');
Volt::route('/users/{user}/edit', 'admin.users.edit')->name('users.edit');
// Consultations
Volt::route('/consultations', 'admin.consultations.index')->name('consultations.index');
Volt::route('/consultations/pending', 'admin.consultations.pending')->name('consultations.pending');
Volt::route('/consultations/calendar', 'admin.consultations.calendar')->name('consultations.calendar');
Volt::route('/consultations/{consultation}', 'admin.consultations.show')->name('consultations.show');
// Timelines
Volt::route('/timelines', 'admin.timelines.index')->name('timelines.index');
Volt::route('/timelines/create', 'admin.timelines.create')->name('timelines.create');
Volt::route('/timelines/{timeline}', 'admin.timelines.show')->name('timelines.show');
// Posts
Volt::route('/posts', 'admin.posts.index')->name('posts.index');
Volt::route('/posts/create', 'admin.posts.create')->name('posts.create');
Volt::route('/posts/{post}/edit', 'admin.posts.edit')->name('posts.edit');
// Settings
Volt::route('/settings/working-hours', 'admin.settings.working-hours')->name('settings.working-hours');
Volt::route('/settings/blocked-times', 'admin.settings.blocked-times')->name('settings.blocked-times');
Volt::route('/settings/content-pages', 'admin.settings.content-pages')->name('settings.content-pages');
// Reports
Volt::route('/reports', 'admin.reports.index')->name('reports.index');
});
```
### 9.2 Route Summary
| Area | Route Prefix | Middleware | Description |
|------|--------------|------------|-------------|
| Public | `/` | none | Home, posts, legal pages |
| Client | `/client` | auth, verified, active | Client dashboard, bookings, timelines |
| Admin | `/admin` | auth, verified, admin | Full management interface |
| Auth | `/` (Fortify) | varies | Login, logout, password, 2FA |
| Settings | `/settings` | auth, verified | User profile settings |
---
## 10. Email System
### 10.1 Configuration
```php
// config/mail.php (via .env)
MAIL_MAILER=smtp
MAIL_HOST=smtp.libra.ps
MAIL_PORT=587
MAIL_USERNAME=no-reply@libra.ps
MAIL_PASSWORD=****
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=no-reply@libra.ps
MAIL_FROM_NAME="${APP_NAME}"
```
### 10.2 Email Templates
| Email Class | Trigger | Recipient | Queue | Attachments |
|-------------|---------|-----------|-------|-------------|
| `WelcomeMail` | User created | Client | Yes | None |
| `BookingSubmittedMail` | Consultation created | Client | Yes | None |
| `BookingApprovedMail` | Consultation approved | Client | Yes | .ics file |
| `BookingRejectedMail` | Consultation rejected | Client | Yes | None |
| `ConsultationReminderMail` | 24h/2h before | Client | Scheduled | .ics file |
| `TimelineUpdatedMail` | Timeline update added | Client | Yes | None |
| `NewBookingRequestMail` | Consultation created | Admin | Yes | None |
### 10.3 Email Template Example
```php
// app/Mail/BookingApprovedMail.php
class BookingApprovedMail extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
public function __construct(
public Consultation $consultation
) {}
public function envelope(): Envelope
{
$locale = $this->consultation->user->preferred_language;
return new Envelope(
subject: $locale === 'ar'
? 'تمت الموافقة على موعدك - مكتب ليبرا للمحاماة'
: 'Your Consultation Approved - Libra Law Firm',
);
}
public function content(): Content
{
return new Content(
markdown: 'emails.booking-approved',
with: [
'consultation' => $this->consultation,
'user' => $this->consultation->user,
'locale' => $this->consultation->user->preferred_language,
],
);
}
public function attachments(): array
{
$calendar = app(CalendarService::class);
return [
Attachment::fromData(
fn () => $calendar->generateIcs($this->consultation),
'consultation.ics'
)->withMime('text/calendar'),
];
}
}
```
### 10.4 Email Template Structure (Blade)
```blade
{{-- resources/views/emails/booking-approved.blade.php --}}
{{ config('app.name') }}