Everything about how Law Farm is built — layers, database schema, every frontend and admin module, the AI service layer, settings, and security. For install steps, see the Setup Guide instead.
Controllers stay thin — they delegate to Form Requests for validation, Repositories or Services for logic, and Eloquent for persistence.
| Layer | Location | Responsibility |
|---|---|---|
| Controllers | app/Http/Controllers/{Frontend,Admin,Api} | HTTP entry points; thin, delegate out |
| Form Requests | app/Http/Requests/{Frontend,Admin} | Validation & input normalization |
| Repositories | app/Repositories/{Contracts,Eloquent} | Query logic — Blog & Appointment fully implemented as the reference pattern |
| Services | app/Services, app/Services/Ai | AI provider calls, email sending, settings-driven runtime config |
| Models | app/Models | Eloquent models, relationships, scopes, casts |
| Views | resources/views/{frontend,admin} | Server-rendered Blade, Tailwind via CDN |
artisan, bootstrap/, config/*.php, public/index.php, resources/css, resources/js, and vite.config.js are unmodified files from the real, official laravel/laravel 12.0.0 skeleton — not hand-reconstructed.
| Table | Purpose | Key Relationships |
|---|---|---|
users | Admin/staff accounts | belongsToMany roles |
roles / permissions | RBAC | many-to-many via pivots |
practice_areas | Legal service categories | belongsToMany lawyers, hasMany faqs |
lawyers | Attorney profiles | belongsToMany practice_areas, hasMany appointments |
categories / tags | Blog taxonomy | categories hasMany blogs; tags belongsToMany blogs |
blogs | CMS articles | belongsTo category/user, belongsToMany tags |
appointments | Consultation bookings | belongsTo lawyer, practice_area |
contacts | Contact form submissions | — |
faqs | Practice-area FAQs | belongsTo practice_area |
testimonials | Client reviews | — |
settings | Key/value config (7 groups + AI) | — |
ai_logs | AI usage & token tracking | belongsTo user |
email_logs | Outgoing email history | — |
audit_logs | Admin activity trail | belongsTo user, polymorphic auditable |
cache, cache_locks, jobs, and failed_jobs migrations are kept as-is and not duplicated. The old migration-conflict workaround (deleting the skeleton's default users migration) is no longer needed — it's already resolved in this project.
| Module | Route(s) | Notes |
|---|---|---|
| Home | / | Featured practice areas, lawyers, testimonials, latest posts |
| About | /about | Firm bio + team grid |
| Practice Areas | /practice-areas, /practice-areas/{slug} | Detail page shows assigned lawyers + FAQs |
| Lawyers | /lawyers, /lawyers/{slug} | Full bio, experience, practice areas |
| Blog | /blog, /blog/{slug} | Only published posts with published_at ≤ now() are visible |
| FAQ | /faq | Grouped by practice area |
| Contact | /contact | Rate-limited 5/min, writes to contacts |
| Appointment Booking | /appointment | Checks for lawyer double-booking on the selected date |
| Search | /search?q= | Searches published blog title/excerpt/content |
| Newsletter | POST /newsletter/subscribe | Fires NewsletterSubscribed event — wire up a listener for your ESP |
| AI Chatbot Widget | POST /api/chatbot/message | Floating widget in the site footer; hides automatically when AI is off |
All routes below are prefixed /admin and protected by the auth middleware; user/role/category/tag management additionally requires the admin role.
| Module | Route Name Prefix | Role Required |
|---|---|---|
| Dashboard | admin.dashboard | Any authenticated user |
| Users & Roles | admin.users.*, admin.roles.* | admin |
| Lawyers | admin.lawyers.* | Any authenticated user |
| Practice Areas | admin.practice-areas.* | Any authenticated user |
| Blog / CMS | admin.blogs.*, admin.categories.*, admin.tags.* | Any authenticated user; categories/tags: admin |
| Appointments | admin.appointments.* | Any authenticated user |
| Contact Messages | admin.contacts.* | Any authenticated user |
| Testimonials | admin.testimonials.* | Any authenticated user |
| Media Manager | admin.media.* | Any authenticated user |
| SEO | admin.seo.* | Any authenticated user |
| Audit Logs | admin.audit-logs.index | Any authenticated user |
| Settings | admin.settings.* | Any authenticated user |
| AI Settings & Logs | admin.ai.* | Any authenticated user |
role: middleware or the included BlogPolicy pattern as you assign real staff roles.
| Feature | Service | Trigger |
|---|---|---|
| Blog content drafting | BlogContentService::generateDraft() | "✨ Generate with AI" on the blog form |
| SEO metadata suggestions | BlogContentService::generateMetadata() | "✨ AI Suggest" on the blog form's SEO fields |
| FAQ generation | FaqGenerationService::generate() | POST admin/ai/generate/faq |
| Email drafting | EmailDraftService::draft() | POST admin/ai/generate/email |
| Chatbot (site visitors) | ChatbotService::reply() | Floating widget → POST /api/chatbot/message |
AiService::complete() checks the ai.enabled setting first, then routes to Anthropic's /v1/messages or OpenAI's /v1/chat/completions via plain Http:: calls — no vendor SDK required. Every call, success or failure, is written to ai_logs with token counts where available.
All settings live in a single settings key-value table (Setting::get/set/group()), grouped by the group column. Secrets are stored encrypted.
| Group | Keys | Encrypted? |
|---|---|---|
general | site_name, tagline, contact_email, contact_phone, address | — |
email | smtp_host, smtp_port, smtp_encryption, smtp_username, smtp_password, from_name, from_email | smtp_password |
seo | default_meta_title, default_meta_description, google_analytics_id, robots_txt | — |
social | facebook_url, twitter_url, linkedin_url, instagram_url | — |
security | two_factor_enabled, login_rate_limit, recaptcha_site_key, recaptcha_secret_key | recaptcha_secret_key |
cache | (action only — "Clear Cache Now" runs artisan optimize:clear) | — |
maintenance | enabled, message | — |
ai | enabled, provider, model, api_key | api_key |
web routes (Laravel default)Role/Permission models + CheckRole middleware (role:admin)LogAdminActivity middleware writes every admin write action to audit_logs, redacting passwords, SMTP credentials, AI API keys, and reCAPTCHA secretslogin (5/min per email+IP); inline throttle:5,1 on contact, appointment, newsletter; throttle:20,1 on the public chatbotSetting::set(..., encrypted: true)CheckMaintenanceMode middleware serves a 503 to everyone except authenticated admins when enabledusers; the TOTP enrollment flow itself still needs wiring via pragmarx/google2fa-laravel| Role | Access |
|---|---|
admin | All permissions |
editor | Blogs, Practice Areas, Testimonials |
support | Appointments, Contacts |
All placeholder photography comes from picsum.photos (practice area & blog cover images) and i.pravatar.cc (lawyer, admin, and testimonial headshots) — both free, non-copyrighted placeholder services safe for local development. Replace with real assets via the Media Manager before launch.
Queues default to the database driver; wire Mail::send() calls to ->queue() where async delivery matters.
public disk — no thumbnails or conversionslogin_rate_limit is stored as a setting but not yet read by the actual rate limiter (currently hardcoded to 5/min)newsletter_subscribers table yet — the event fires but isn't persisted or sent to an ESP