Skip to content

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).

EnvironmentStorefrontAdmin (Filament)HorizonMeilisearchAPI base
localhttp://localhost:3000http://localhost:8000/adminhttp://localhost:8000/horizonhttp://127.0.0.1:7700http://localhost:8000/api
stagehttps://stage.easyotc.comhttps://stage-api.easyotc.com/adminhttps://stage-api.easyotc.com/horizonhttps://search.easyotc.comhttps://stage-api.easyotc.com/api
stage-goldkidneyhttps://stage-goldkidney.easyotc.com(uses stage API)(uses stage API)(uses stage Meilisearch)(uses stage API)
productionhttps://easyotc.comhttps://api.easyotc.com/adminhttps://api.easyotc.com/horizonhttps://search-engine.easyotc.comhttps://api.easyotc.com/api

Notes:

  • The admin/Horizon paths come from app/Providers/Filament/AdminPanelProvider.php (->path('admin')) and config/horizon.php (HORIZON_PATH, default horizon).
  • The API domains are pulled from the frontend env files (.env.stage, .env.production) under NUXT_PUBLIC_API_BASE_URL. The API repo's cloud.config.ts is not yet committed — confirm before assuming.
  • The stage-goldkidney site 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_ADMIN for 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.

EmailRole (intended)Notes
superadmin@easyotc.comOTC One AdminCarrier #1
carrier.admin@easyotc.comCarrier AdminCarrier #1
agent@easyotc.comAgentCarrier #1

Roles are not assigned in the seeder itself — after seeding, run:

bash
php artisan roles:setup
php artisan roles:permissions

Then assign in tinker:

bash
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

bash
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 caseValueWhat they can do
OTC_ONE_ADMINotc_one_adminFull CRUD on every resource. Sees all navigation.
CARRIER_ADMINcarrier_adminRead-only. Scoped to their carrier_id for Products & Orders. Can edit Carriers.
AGENTagentRead on Products/Orders/Members. Can edit Orders.
MEMBERmemberStorefront-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

bash
# Pattern
ssh forge@<server-ip-or-hostname>

# Example (stage)
ssh forge@stage-api.easyotc.com

The 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

bash
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:clear

Forge 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 / Queue tab — 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 (set AWS_PROFILE=eliinova).
  • Default region: us-east-1.

Buckets

BucketPurposeReferenced in
stage-otcStage storefront static hosting (Nuxt SSG output)cloud.config.ts (sites.stage)
stage-goldkidneyStage co-branded storefrontcloud.config.ts (sites.stage-goldkidney)
otc-stageStage product/image assets (referenced by Nuxt image provider)nuxt.config.ts image.domains
easyotcProduction product/image assetsnuxt.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:

bash
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

RepoPurpose
Eliinova/the-one-otc-apiLaravel 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

  • main is the deploy branch for both repos.
  • Forge auto-deploys API main on push.
  • Nuxt static deploys are triggered by bun run generate + upload to S3 (see cloud.config.ts → sites.*.build).
  • Feature work goes on short-lived branches, PR into main.

Database

Settings come from .env.example:

KeyDefault (local)Notes
DB_CONNECTIONpgsqlPostgreSQL is the canonical driver
DB_HOST127.0.0.1
DB_PORT3306 (placeholder — Postgres uses 5432, see installation.md)
DB_DATABASEthe_one_otc_api
DB_USERNAMEroot
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

bash
# 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 vars

Seed 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.

bash
php artisan migrate:fresh --seed

Resetting a stage DB

bash
# 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 via HORIZON_PATH, see config/horizon.php).
  • Connection: Redis (QUEUE_CONNECTION=redis in .env.example, plus redis connection in config/queue.php).
  • Worker config: config/horizon.php → environments.{production,stage,local} — 10 processes in production and stage, 3 in local.
  • Access control: routes use web middleware only; the panel is currently open to anyone with a session on that host. If we tighten it, it'll be in app/Providers/HorizonServiceProvider.php.

Is it alive?

bash
# On server
sudo supervisorctl status | grep horizon
php artisan horizon:status   # returns "Horizon is running."

# From a browser
curl -I https://<env-api-host>/horizon

If 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 port 7700), key in MEILISEARCH_KEY.
  • Stage host: https://search.easyotc.com.
  • Production host: https://search-engine.easyotc.com.
  • Driver: Scout (SCOUT_DRIVER=meilisearch, queued via SCOUT_QUEUE=true).

Inspect

bash
# 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/search

Re-index

bash
# Wraps scout:flush + scout:import + scout:sync-index-settings
php artisan app:index-models Product

See 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.