Access Guide
A self-serve reference for getting into every part of the EasyOTC platform: URLs, admin accounts, SSH, AWS, GitHub, and where secrets live. No secret values appear in this document — only the keys/paths that hold them.
If a value is marked "TBC" it has not been confirmed by the team yet; ping a maintainer for the current value.
URLs by environment
Source of truth for domains: cloud.config.ts (frontend repo).
| Environment | Storefront | Admin (Filament) | Horizon | Meilisearch | API base |
|---|---|---|---|---|---|
| local | http://localhost:3000 | http://localhost:8000/admin | http://localhost:8000/horizon | http://127.0.0.1:7700 | http://localhost:8000/api |
| stage | https://stage.easyotc.com | https://stage-api.easyotc.com/admin | https://stage-api.easyotc.com/horizon | https://search.easyotc.com | https://stage-api.easyotc.com/api |
| stage-goldkidney | https://stage-goldkidney.easyotc.com | (uses stage API) | (uses stage API) | (uses stage Meilisearch) | (uses stage API) |
| production | https://easyotc.com | https://api.easyotc.com/admin | https://api.easyotc.com/horizon | https://search-engine.easyotc.com | https://api.easyotc.com/api |
Notes:
- The admin/Horizon paths come from
app/Providers/Filament/AdminPanelProvider.php(->path('admin')) andconfig/horizon.php(HORIZON_PATH, defaulthorizon). - The API domains are pulled from the frontend env files (
.env.stage,.env.production) underNUXT_PUBLIC_API_BASE_URL. The API repo'scloud.config.tsis not yet committed — confirm before assuming. - The
stage-goldkidneysite is a co-branded storefront pointing at the same stage API.
Admin panel access
- URL pattern:
/{env-api-host}/admin(see table above). - Required role:
OTC_ONE_ADMINfor full access. Other roles see a filtered panel — see role matrix below. - Login form: Filament default at
/admin/login.
Seeded accounts (fresh DB only)
Created by database/seeders/UserSeeder.php. All use the same dev password defined inline in the seeder — rotate it before any non-local environment.
| Role (intended) | Notes | |
|---|---|---|
superadmin@easyotc.com | OTC One Admin | Carrier #1 |
carrier.admin@easyotc.com | Carrier Admin | Carrier #1 |
agent@easyotc.com | Agent | Carrier #1 |
Roles are not assigned in the seeder itself — after seeding, run:
php artisan roles:setup
php artisan roles:permissionsThen assign in tinker:
php artisan tinker
>>> $u = App\Models\User::where('email','superadmin@easyotc.com')->first();
>>> $u->assignRole(App\Enums\RoleEnum::OTC_ONE_ADMIN->value);Creating an admin from scratch
php artisan tinker
>>> $u = App\Models\User::create([
... 'first_name' => 'Admin',
... 'last_name' => 'Last',
... 'email' => 'pujen@example.com',
... 'password' => 'change-me-now',
... 'email_verified_at' => now(),
... 'carrier_id' => 1,
... ]);
>>> $u->assignRole(App\Enums\RoleEnum::OTC_ONE_ADMIN->value);Available roles
From app/Enums/RoleEnum.php:
| Enum case | Value | What they can do |
|---|---|---|
OTC_ONE_ADMIN | otc_one_admin | Full CRUD on every resource. Sees all navigation. |
CARRIER_ADMIN | carrier_admin | Read-only. Scoped to their carrier_id for Products & Orders. Can edit Carriers. |
AGENT | agent | Read on Products/Orders/Members. Can edit Orders. |
MEMBER | member | Storefront-side role. No admin panel access. |
Resource-level rules live on each Filament resource (e.g. app/Filament/Resources/ProductResource.php — see canAccess, canCreate, canEdit, canDelete, canView). Full matrix in docs/role-based-access-control.md.
Server access (Forge / SSH)
Hosting is Laravel Forge on an Ubuntu VPS (see docs/advanced/deployment.md).
SSH
# Pattern
ssh forge@<server-ip-or-hostname>
# Example (stage)
ssh forge@stage-api.easyotc.comThe SSH user is always forge. Ask in the Forge team to have your public key added — host IPs are in the Forge dashboard under the server detail page.
Release path
Code is deployed via Forge's zero-downtime deploys (Envoyer-style symlinks):
/home/forge/<domain>/current # symlink to the active release
/home/forge/<domain>/releases # all retained releases
/home/forge/<domain>/storage # shared storage (logs, app data)
/home/forge/<domain>/shared/.env # environment variables (managed via Forge UI)Example for stage: /home/forge/stage-api.easyotc.com/current.
Common ops
cd /home/forge/<domain>/current
# Tail Laravel app log
tail -f storage/logs/laravel.log
# Tail Horizon worker output (via supervisor / journalctl)
sudo supervisorctl status
sudo supervisorctl tail -f horizon-<name> stderr
# Restart Horizon (graceful)
php artisan horizon:terminate
# Clear caches
php artisan optimize:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clearForge web UI
Most things are easier through the Forge dashboard at https://forge.laravel.com/ :
- Edit env vars (
Sites → <site> → Environment) - Trigger deploys (
Deploy Now) - Manage queue workers (
Daemons/Queuetab — Horizon is normally run as a daemon) - View deploy logs and SSL cert status
AWS
From cloud.config.ts and the frontend .env.* files.
- AWS CLI profile:
eliinova(setAWS_PROFILE=eliinova). - Default region:
us-east-1.
Buckets
| Bucket | Purpose | Referenced in |
|---|---|---|
stage-otc | Stage storefront static hosting (Nuxt SSG output) | cloud.config.ts (sites.stage) |
stage-goldkidney | Stage co-branded storefront | cloud.config.ts (sites.stage-goldkidney) |
otc-stage | Stage product/image assets (referenced by Nuxt image provider) | nuxt.config.ts image.domains |
easyotc | Production product/image assets | nuxt.config.ts image.domains |
<aws-bucket-env> | API uploads (S3 disk). Bucket name lives in AWS_BUCKET env on Forge. | config/filesystems.php |
The production storefront bucket is not explicitly named in cloud.config.ts — Stacks/ts-cloud derives it from the project slug (easyotc); confirm in the AWS console.
IAM scope needed for full debug
To get-in-and-do-anything you'll want an IAM user / SSO role with:
AmazonS3FullAccess(read/write on the buckets above)CloudFrontFullAccess(invalidations after deploys)AmazonRoute53ReadOnlyAccess(DNS lookups — though DNS provider is Cloudflare, not Route53)AWSCertificateManagerReadOnly(for ACM certs used by CloudFront)IAMReadOnlyAccess(so you can see who else has what)
A grouped AdministratorAccess policy works too if the team is OK with that.
CloudFront
Each public bucket sits behind a CloudFront distribution. Distribution IDs are not committed to the repo — find them in the AWS console under CloudFront → Distributions, filtered by the bucket origin. After a static deploy you typically run:
aws --profile eliinova cloudfront create-invalidation \
--distribution-id <id> --paths "/*"DNS
DNS is on Cloudflare (infrastructure.dns.provider: 'cloudflare' in cloud.config.ts). The token lives in CLOUDFLARE_API_TOKEN in the frontend .env.production.
GitHub
| Repo | Purpose |
|---|---|
Eliinova/the-one-otc-api | Laravel API + Filament admin (this repo) |
Eliinova/<frontend-repo> (Nuxt — confirm exact name) | Storefront (easty-otc working dir locally) |
The frontend repo name isn't recorded in either repo's config — check the git remote of the local easty-otc checkout for the canonical URL.
Branch model
mainis the deploy branch for both repos.- Forge auto-deploys API
mainon push. - Nuxt static deploys are triggered by
bun run generate+ upload to S3 (seecloud.config.ts → sites.*.build). - Feature work goes on short-lived branches, PR into
main.
Database
Settings come from .env.example:
| Key | Default (local) | Notes |
|---|---|---|
DB_CONNECTION | pgsql | PostgreSQL is the canonical driver |
DB_HOST | 127.0.0.1 | |
DB_PORT | 3306 (placeholder — Postgres uses 5432, see installation.md) | |
DB_DATABASE | the_one_otc_api | |
DB_USERNAME | root | |
DB_PASSWORD | (empty in example; set per env) |
Production / stage credentials are stored in the Forge Environment tab on the relevant site — never in the repo.
Connecting via TablePlus / psql
# Local
psql -h 127.0.0.1 -p 5432 -U root the_one_otc_api
# Stage/prod: open an SSH tunnel through Forge first
ssh -L 5432:127.0.0.1:5432 forge@<server-host>
# then connect TablePlus to 127.0.0.1:5432 with the creds from Forge env varsSeed data
Loaded by database/seeders/DatabaseSeeder.php. Includes carriers, users, mailing lists, tags, product images, purse categories, members, messages, issues, purses, and promotions. ProductSeeder, OrderSeeder, OrderItemSeeder, and CategoryAndCategorizableSeeder are currently commented out.
php artisan migrate:fresh --seedResetting a stage DB
# On the stage server
cd /home/forge/stage-api.easyotc.com/current
php artisan down
php artisan migrate:fresh --seed --force
php artisan app:index-models Product # rebuild Meilisearch
php artisan up--force is required in non-local envs. Confirm with the team before running — this wipes everything.
Queue / Horizon
- Dashboard:
/horizon(path configurable viaHORIZON_PATH, seeconfig/horizon.php). - Connection: Redis (
QUEUE_CONNECTION=redisin.env.example, plusredisconnection inconfig/queue.php). - Worker config:
config/horizon.php → environments.{production,stage,local}— 10 processes in production and stage, 3 in local. - Access control: routes use
webmiddleware only; the panel is currently open to anyone with a session on that host. If we tighten it, it'll be inapp/Providers/HorizonServiceProvider.php.
Is it alive?
# On server
sudo supervisorctl status | grep horizon
php artisan horizon:status # returns "Horizon is running."
# From a browser
curl -I https://<env-api-host>/horizonIf jobs aren't draining: php artisan horizon:terminate to recycle workers (supervisor will respawn).
Search (Meilisearch)
- Local host:
MEILISEARCH_HOST=http://127.0.0.1:7700(default port7700), key inMEILISEARCH_KEY. - Stage host:
https://search.easyotc.com. - Production host:
https://search-engine.easyotc.com. - Driver: Scout (
SCOUT_DRIVER=meilisearch, queued viaSCOUT_QUEUE=true).
Inspect
# List indexes
curl -H "Authorization: Bearer $MEILISEARCH_KEY" \
https://search.easyotc.com/indexes
# Search the products index
curl -X POST -H "Authorization: Bearer $MEILISEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{"q":"vitamin"}' \
https://search.easyotc.com/indexes/products/searchRe-index
# Wraps scout:flush + scout:import + scout:sync-index-settings
php artisan app:index-models ProductSee app/Console/Commands/IndexModels.php. Pass any model class name under App\Models\ (e.g. Member, Order).
Secrets handling
Never commit secrets. Production and stage env vars live in the Forge dashboard (Sites → <site> → Environment) and are written to /home/forge/<domain>/shared/.env on the server — edit them only through Forge so they survive deploys. Local secrets go in .env files (gitignored); .env.example lists every key but no values. Frontend static deploys read build-time vars from the local .env.{stage,production} files when running bun run generate, so those files are kept on a maintainer's machine or pulled from 1Password — they are gitignored. AWS access for the CLI uses the eliinova profile in ~/.aws/credentials; no AWS keys should ever appear in either repo's env files (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY exist as keys in .env.example for SDK use but stay empty unless we move off IAM-role-based access). No AWS Secrets Manager integration is wired up today — if/when we add one, document the secret ARNs here.