Package: demo
Generated from api/packages/demo
Source: api/packages/demo. The sections after the README are extracted from the code on every docs build.
README
Section titled “README”What does it do
Section titled “What does it do”Turns a tenant into a public demo that anyone can try without an account:
POST /api/demo/login {"role": "student" | "tutor" | "admin"}logs the visitor in as the tenant’s seeded student, tutor or admin, without a password. It issues a regular Passport personal access token and answers with the same body asPOST /api/auth/login, so the front and the admin store it like after a normal login. The demo student is first given access to every published course, so any course link opens with access.GET /api/demoreturns{enabled: true, users: [{role, email}], front_url, admin_url, reset_cron}. It never returns passwords.GET /api/configgainsulams_demo: {enabled, front_url, admin_url}. The front and the admin read it at boot and log in automatically (front as the student, admin as the admin).ulams:demo:resetwipes the tenant and seeds it again; the scheduler runs it hourly.
Demo mode has no security. Anyone who can reach the API can act as the tenant admin. Turn it on only for throwaway demo tenants whose data is wiped every hour, never for a tenant with real data, and never on the platform.
Turning it on
Section titled “Turning it on”Demo mode is on when the tenant’s .env.<host> has DEMO_MODE=true. Default is off: then the
routes do not exist (404), nothing is added to /api/config, and no reset is scheduled.
Set it through the tenant registry so that ulams:tenant:sync-env keeps it:
php artisan ulams:tenant:create coffee --demo=on # or --demo=off; only the env file is rewrittenmake demo-mode-on # the six demo tenants: coffee, oncall, nightsky, gravity, poland, ulamAccounts
Section titled “Accounts”| Role | Account | Override |
|---|---|---|
| admin | INITIAL_USER_EMAIL (admin@<slug>.ulams.app, created by PermissionsSeeder) |
DEMO_ADMIN_EMAIL |
| student | student1@<admin e-mail domain> (created by ulams:tenant:seed-demo) |
DEMO_STUDENT_EMAIL |
| tutor | tutor@<admin e-mail domain> (created by ulams:tenant:seed-demo; the course author in the studio) |
DEMO_TUTOR_EMAIL |
If the account does not exist, the first user with that role is used.
Hourly reset
Section titled “Hourly reset”php artisan ulams:demo:reset --force --domain=coffee.localhostmake demo-reset DOMAIN=coffee.localhostThe command refuses to run unless DEMO_MODE=true on that domain, and always refuses on the
platform. First it deletes the tenant’s H5P content in the H5P service (api/h5p), in
process. The service keeps that content in schema h5p of the tenant database and in the
tenant bucket, which migrate:fresh does not touch, so without this step every reset would
add another copy of the demo content:
DELETE /h5p/contents/{id}for every id the tenant database knows (h5p.contentsrows andtopic_h5ps.value),POST /h5p/contents/orphans/delete: files of contents that have no row any more.
Both calls go through the h5p package’s client, which sends the tenant host and the internal
token; the service scopes them to that tenant’s schema and bucket. If the service cannot be
reached, the reset goes on with a warning: the rows stay in h5p.contents, so the next reset
deletes them.
Then it runs these steps, each as a child php artisan … --domain=<host>:
migrate:fresh --force: the whole tenant database, including the OAuth tables, so every token issued so far stops working. The front and the admin then log in again by themselves.passport:client --personal(the personal access client lived in the wiped database).db:seed --class=PermissionsSeeder(roles and the admin account).ulams:tenant:seed-demowith the baseline (below): tutor, students, name, theme, accent.ulams:demo:seed: the demo courses (DemoCoursesSeeder, experience = tenant slug) and access to every published course for the demo student.
The tenant’s Redis cache keys are deleted after step 1 and at the end. cache:clear is not
used because the Redis cache store is shared by all tenants. With --wipe-files (or
DEMO_RESET_WIPE_FILES=true) every file in the tenant bucket is deleted first; by default
uploads stay, and the demo assets are uploaded again at the same paths.
Baseline. Name, theme, accent, front URL, number of students and the e-mail domain live
in the database being wiped. The first reset captures them into
the tenant storage directory
(storage/coffee_localhost/app/ulams-demo-baseline.json), and later resets reuse that file. A theme
change made in the demo admin therefore does not survive the next reset.
--recapture takes a new baseline from the current database.
Schedule. The provider schedules ulams:demo:reset --force --domain=<host> hourly
(DEMO_RESET_CRON, default 0 * * * *) on every domain whose env has DEMO_MODE=true.
scheduler.sh already runs schedule:run --domain=<host> for every tenant, so nothing else
needs to be set up. The command runs in the background, without overlapping.
ulams:demo:seed --skip-content only grants the student access to the published courses, for
example after courses were created by hand.
Configuration
Section titled “Configuration”See src/config.php and the demo mode section of docs/enviromental-variables.md.
vendor/bin/phpunit --testsuite demoThe feature tests never wipe the test database: the reset steps run through a fake command runner. The tenant isolation test switches the Passport key pair to show that a token of one tenant is rejected by another. The end-to-end check against the running demo tenants is opt-in:
docker compose exec -T -e DEMO_INTEGRATION=1 api vendor/bin/phpunit packages/demo/tests/IntegrationDemoResetH5PTest also needs DEMO_INTEGRATION_RESET=1: it resets coffee.localhost twice
and checks that the H5P content count is the same after both resets.
API endpoints
Section titled “API endpoints”| Method | Path | Auth | Action |
|---|---|---|---|
GET |
/api/demo |
DemoApiController@show |
|
POST |
/api/demo/login |
DemoApiController@login |
Permissions
Section titled “Permissions”None.
Settings
Section titled “Settings”Registered with AdministrableConfig::registerConfig (editable in the admin panel under Configuration → Settings).
| Key | Rules | Public | Read-only |
|---|---|---|---|
ulams_demo.<key> |
nullable | yes | yes |
Events
Section titled “Events”None.
Artisan commands
Section titled “Artisan commands”| Command | Description | Signature |
|---|---|---|
ulams:demo:reset |
Wipe and reseed this demo tenant (database, demo users, demo courses, cache, tokens). Requires DEMO_MODE=true. | ulams:demo:reset {--force : Do not ask for confirmation} {--wipe-files : Also delete every file on the tenant\'s default disk (bucket)} {--recapture : Take a new baseline (name, theme, accent, students) from the current database} |
ulams:demo:seed |
Seed the demo courses of this tenant and give the demo student access to every published course | ulams:demo:seed {--experience= : Demo experience to seed (coffee, oncall, nightsky, gravity, poland, ulam or all); defaults to ULAMS_DEMO_EXPERIENCE, then the tenant slug} {--skip-content : Only grant the demo student access to the published courses} |
Scheduled jobs
Section titled “Scheduled jobs”| Kind | Target | Frequency |
|---|---|---|
| command | ) renders ''--force' => true' as '--force=1', // which Symfony rejects for an option without a value (the reset then never ran) $parameters = ['--force']; $domain = method_exists($this->app, 'domain') ? $this->app->domain() : null; if (is_string($domain) && $domain !== '') { $parameters['--domain'] = $domain; } $schedule->command(ResetDemoCommand::class, $parameters |
cron((string) config(self::CONFIG_KEY . '.reset.cron', '0 * * * *'))->withoutOverlapping(120)->runInBackground() |
Environment variables read
Section titled “Environment variables read”ADMIN_URL, DEMO_ADMIN_EMAIL, DEMO_ADMIN_URL, DEMO_CONTENT_SEEDER, DEMO_MODE, DEMO_RESET_CRON, DEMO_RESET_SCHEDULE, DEMO_RESET_STUDENTS, DEMO_RESET_WIPE_FILES, DEMO_STUDENT_EMAIL, DEMO_TUTOR_EMAIL, FRONTEND_URL, INITIAL_USER_EMAIL, TENANT_SLUG, ULAMS_DEMO_EXPERIENCE