ORVIAN is a multi-tenant monolith in which each school is its own fully isolated tenant. The tenant identifier isDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Elian-D/ORVIAN/llms.txt
Use this file to discover all available pages before exploring further.
school_id — a foreign key present on every Tenant/ model and on the users table. Rather than separate databases or schemas, ORVIAN enforces isolation at the query layer using an Eloquent global scope, so every query against tenant data is automatically filtered to the school that owns the active session. Tenant context is resolved once at the middleware layer from the authenticated user and then made available application-wide.
How Tenant Isolation Works
SchoolScope — the query filter
App\Models\Scopes\SchoolScope is an Eloquent Scope that is registered as a global scope on every model inside app/Models/Tenant/. When a Tenant/ model is queried, SchoolScope::apply() inspects the currently authenticated user and appends a WHERE clause before the query reaches the database:
Student::all(), AttendanceSession::where(...), or any other Tenant/ model query automatically appends WHERE school_id = <current_school_id> — no manual filtering required in services or Livewire components.
Spatie Permissions — team-scoped roles
ORVIAN usesspatie/laravel-permission with teams = true. The “team” concept maps directly to school_id. When the IdentifyTenant middleware runs, it calls:
hasRole() / hasPermissionTo() check to that school’s roles, preventing a Teacher at one school from inheriting permissions that belong to a Teacher at a different school.
User Types and Tenant Context
Every user in ORVIAN has exactly one login path. The branching decision is made inAuthenticatedSessionController@store immediately after authentication succeeds, based on whether school_id is null:
school_id value | User types | Post-login redirect |
|---|---|---|
null | Owner, TechnicalSupport, Administrative | /admin/hub (admin.hub) |
| Non-null | School Principal (Director), Academic Coordinator, Teacher, Secretary, Student, Staff | /app/dashboard (app.dashboard) |
school_id is stored in session('impersonated_school_id'). SchoolScope and IdentifyTenant both check this session key as a fallback, so impersonating users see exactly the data that school’s tenant users see — without needing a non-null school_id on their user record.
Platform Admin vs. School Panel
ORVIAN splits its front end into two completely separate route groups, each protected by its own middleware stack:/admin/* — Platform Admin Panel
admin.global alias maps to EnsureGlobalAdminAccess. It blocks any user whose school_id is not null from entering the admin panel and redirects them to app.dashboard:
SchoolScope is also automatically bypassed on admin/* routes. The Role model registers an additional anonymous global scope that strips SchoolScope from the builder whenever the request path starts with admin/, so admin users can manage roles and data across all schools without interference from tenant filtering.
/app/* — School Panel
IdentifyTenant middleware (appended globally to the web middleware stack in bootstrap/app.php) sets the Spatie team ID and resolves the current School instance into the service container (app()->instance('currentSchool', $school)), making it available anywhere in the request lifecycle.
School Lifecycle
A school goes through a deterministic lifecycle from self-registration to full operation:1. Stub creation
When a new school self-registers through the public landing page, a minimal user account is created. At this point the school’s record does not yet exist — the user is treated as a platform user pending onboarding.2. Wizard completion
The registered user is directed to the Tenant Setup Wizard (/wizard, TenantSetupWizard Livewire component). The wizard collects school metadata (SIGERD code, name, modality, academic levels, shifts) and plan selection. On submission, CompleteOnboardingAction::execute() runs inside a database transaction:
- Creates the
Schoolrecord withis_configured = trueandis_active = true. - Syncs academic levels (
levels()->sync()). - Creates shift records with MINERD-standard times.
- Calls
SchoolRoleService::seedDefaultRoles($school)to clone all base roles for the new tenant. - Calls
CreateSchoolPrincipalAction::execute()to create the Director user and assign them theSchool Principalrole within the school’s team scope. - Fires the
SchoolConfiguredevent, carrying the newSchoolinstance and academic setup data.
3. SchoolConfigured event
CreateInitialAcademicYear) and scaffolding the academic structure (SetupAcademicStructure). These run asynchronously via the database queue so the HTTP response is not blocked.
The
EnsureSchoolIsActive middleware (registered as school.active) runs on every /app/* request. If the school’s is_active flag is false, the user is redirected to app.notice.inactive. If is_suspended is true (e.g., overdue payment), the user is redirected to app.notice.suspended. Both notice routes are defined outside the restrictive middleware group to prevent redirect loops.