District Core Developer DocsDistrict Core Developer Docs
Developers
Boilerplate
Modules
Bitbucket
Developers
Boilerplate
Modules
Bitbucket
  • Modules

    • ABN
    • ActivityLog
    • AnalyticsApi
    • ApiConnector
    • BlockApi
    • CategoryApi
    • CloneApi
    • CommentApi
    • ContentApi
    • Core
    • Documents
    • EmbedApi
    • Event
    • ExportApi
    • FeatureApi
    • FormApi
    • GTM
    • GalleryApi
    • HelpApi
    • Hotspot
    • IdeaSurvey
    • ImportApi
    • InteractionsApi
    • Intercom
    • MailApi
    • MapApi
    • MapSurvey
    • MediaApi
    • MenuApi
    • MetaTagApi
    • NlpApi
    • NotificationApi
    • Page
    • ParentableContent
    • PaymentApi
    • PermissionsApi
    • Postcode
    • ReCaptcha
    • Redirects
    • Renderer
    • ReportApi
    • RestrictionApi
    • RevisionApi
    • SearchApi
    • Settings
    • ShareableApi
    • Slack
    • SlugApi
    • SubscribableApi
    • Survey
    • Team
    • TenantApi
    • TestApi
    • ThemeApi
    • Timeline
    • TranslationApi
    • Update
    • Users
    • VisualisationApi
    • WorkflowApi
    • Wysiwyg

TenantAPI module

Handles multi-tenant in the application via Tenancy. You should read the Docs to get an understanding of how things work.

Setup used

  • Multi-database tenancy (each tenant has their own database)
  • Central site (holds the tenancy information)

Quick start

Database modes

This comes down to; does the laravel database user have permissions to create new databases or not. If so, use Own database if not use Table prefixing. Changing this after tenants have been created will break those tenants if you have not migrated the data to the new format too.

Really, just always use Own Database unless you have a valid reason not to.

Own Database (default)

This is the preference and should be used for ALL production sites. It means that each tenant will have their own database and no table prefixes are used.

To set this TENANCY_DATABASE_TYPE=own_database

Table prefixing (testing only)

The tenancy package has been extended via MySQLDatabaseManager to support table prefixes. This results in only a single database being required and each table has t_TENANTID_ prefixed. Due to a max length on table/index names of 64 characters it is key that table/index names account for this prefix. It should only be 8 characters, but that means a table/index name cannot be longer than 56 chars, so make sure you define your index names manually!

This mode should only be used for testing and results in the need for only ONE database.

To set this TENANCY_DATABASE_TYPE=prefix

Prepping databases

  • lando artisan migrate:fresh --seed = To migrate fresh and seed the central database
  • lando artisan db:seed Modules\\TenantApi\\Database\\Seeders\\TenantDemoSeeder - Seed some demo tenants (this will create a database for each tenant). See seeder class for test domains eg tenant.01.district-core.lndo.site
  • (optional) lando tenant:migrate-fresh - To create all the tables in the tenant databases. This gets done with the above command but useful for testing.
  • lando artisan tenant:seed - To seed content to all the tenants

Creating new tenants (Testing / POC)

IMPORTANT: Tenant databases/migrations get created via jobs. You should have lando artisan horizon running.

Visit central.district-core.lndo.site/central and add a new domain name. Click "Create new tenant" and watch horizon until seeding complete.

Visit the new tenant domain!

Digging deeper

Queues and Jobs

Any tasks that may be queued, eg implements Illuminate\Contracts\Queue\ShouldQueue should also implement TenantAwareJobInterface and use TenantAwareJobTrait. This will add the required tag() method that identifies the tenant the job belongs to.

Routing and middleware

When exposing a route, consider whether it should be accessible by:

  • Central domains
  • Tenants
  • Both

By default, all routes are exposed to both central and tenant domains. Core/Providers/BaseRouteServiceProvider wraps all routes in the tenant group middleware that will identify the current tenant via the domain used. The same routes are also defined "domain specific" for each central domain and without this middleware to allow central access.

This behaviour can be overridden if you:

  • You place your route in YourModule/Routes/central-dash for central only dashboard routes
  • You place your route in YourModule/Routes/central-web for central only web routes
  • Wrap your route in the tenant.only middleware for tenant only routes.

Note that routes that are defined by third party modules (i.e fortify) are not domain specific and need both the tenant group and universal group. See Universal Routes

The middleware InitializeTenancyByDomain and PreventAccessFromCentralDomainshave been set as high priority to run before all other route middleware and identify tenants really early.

Events

All events are defined in tenancy.events config and listeners may be overwritten my other modules. This module provides essential listeners for tenant create & delete. The TenancyServiceProvider will bind all the events and listeners during boot.

Tests

Tests ideally run in the context of a tenant as that is where the bulk of the functionality lives. To do this, some extra trickery is required as we need to handle both tenant unique databases AND the ability to have these created in parallel.

Most of this has been solved for you already, and ideally you just need two things:

  • Ensure your test extends Modules\TenantApi\Tests\Base\TenantTestCase

If your test is to run on "central" then you don't need the above.

Example of a test that uses tenants, or specifically uses a get/post to a route:

class MyTest extends TestCase 
{
    use RefreshDatabase;

    protected bool $tenancy = true;
    
    public function test_something()
    {
      //
    }
}

TODO

  • Restrict what gets seeded for central database
  • Test, test, test. Especially with
    • Files/storage
    • Jobs
    • Caches
    • Cookies + Login
  • Check permissions https://tenancyforlaravel.com/docs/v3/integrations/spatie#laravel-permission
  • Ideally use events to prevent the District Core module from relying on this module since all District modules depend on Core.
  • Limit Central domains to a single one AND/OR change how menu URLs are built from FE to Backend instead to avoid issues with multiple Central domains clashing with named routes.

Edit this page
Prev
Team
Next
TestApi