# Technical documentation — laravel

Evidence date: 2026-10-10.

Source: `git@bitbucket.org:overdrive/laravel.git`, `master` at `10c0b9fa` (2023-08-15, Wessels, "Restore prod .env").

`vendor/`, `bvendor/`, `public/` (154,196 files), and `storage/` (34,145 files) were not checked out. The index reported about 210,085 paths. Application source, config, routes, migration filenames, scripts, tests, and root files were read. Secret values from `.env` and from `config/application.php` are not reproduced.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP `>=7.0.0` in `composer.json`. Root `.htaccess` sets cPanel `ea-php70` |
| Framework | Laravel 5.5.50 (`composer.lock` package `laravel/framework`) |
| Web UI | Blade under `resources/views/`. Laravel Mix 1 compiles a Vue 2 example that is not the trading UI |
| API auth | `tymon/jwt-auth` `dev-develop`. Guard `api` driver `jwt` |
| Roles | `spatie/laravel-permission` ^2.6 |
| Database | MySQL, charset `utf8mb4`, collation `utf8mb4_unicode_ci`, `strict` false. Prefix from `DB_PREFIX` |
| Tests | PHPUnit ~6. Example tests only. No project `phpunit.xml` |
| PDF | TCPDF included from `app/Library/tcpdf/tcpdf.php` |
| Excel | `maatwebsite/excel` ~2.1 |

`package.json` devDependencies: `axios` ^0.17, `bootstrap-sass` ^3.3.7, `jquery` ^3.2, `laravel-mix` ^1.0, `lodash` ^4.17.4, `vue` ^2.5.7, `cross-env` ^5.1.

## Application structure

```text
app/Console/Commands     Artisan jobs
app/Events               Firebase update events
app/Http/Controllers     Web controllers
app/Http/Controllers/Admin
app/Http/Controllers/Api
app/Http/Controllers/Auth
app/Http/Middleware      user, admin, JWT, CSRF, XSS
app/Library/FirebaseFCM.php
app/Library/tcpdf        Bundled PDF library
app/Listeners            Firebase listeners
app/Models               102 model classes. Lot extends Listing
app/Rules                Form-request style validators
app/Helpers/Helpers.php  Composer files autoload
config/application.php   Domain constants
routes/web.php           Web UI
routes/api.php           Prefix /api
database/migrations      238 files, latest names dated 2022-06-23
resources/views          Blade areas listed below
scripts                  CodeDeploy hooks
```

View directories: `admin`, `agent_load`, `bin_bulk_lot`, `bin_offer_request`, `bulk_lot`, `buyer`, `buyer_request`, `calculator_widget`, `common`, `email_templates`, `errors`, `layouts`, `marketplace`, `models`, `order`, `poa_lot`, `relief_dashboard`, `reports`, `requests`, `seller_location`, `seller_lot`, `setting`, `special_donation_lot`, `tender`, `transport`, `user`, `vendor`. Root Blade files include `login`, `register`, `dashboard`, `forgot_password`, and the Firebase views.

`public/` top level (names only): `.htaccess`, `.well-known`, `assets`, `css`, `favicon.ico`, `index.php`, `js`, `market`, `my-file.html`, `plugins`, `robots.txt`, `script`, `test.html`, `test_image_rotate.php`, `uploads`, `web.config`. File contents under `public/` were not read, except by this name list.

`storage/` top level: `app`, `framework`, `logs`. Contents were not read.

There is no root README, no `.env.example`, no `bitbucket-pipelines.yml`, and no `phpunit.xml`.

## HTTP

`RouteServiceProvider` loads `routes/web.php` with middleware `web` and namespace `App\Http\Controllers`. It loads `routes/api.php` with prefix `api`, middleware `api`, and the same namespace.

`web` group: encrypt cookies, queued cookies, session, shared errors, CSRF, bindings, `XSSProtection`. `AuthenticateSession` is commented out.

`api` group: `throttle:1000,1`, bindings. No CSRF.

Route middleware aliases: `auth`, `auth.basic`, `bindings`, `can`, `guest`, `throttle`, `role` (Spatie), `admin`, `user`, `check-jwt`.

CSRF exceptions: `lot/upload_lot_image` only.

`routes/web.php` calls `Artisan::call('view:clear')` on every request when `APP_ENV` is `local`.

`routes/channels.php` and `routes/console.php` exist. Channel authorization was not line-reviewed. `console.php` is required from `Kernel::commands`.

## Authentication

| Guard | Driver | Provider |
|---|---|---|
| `web` (session pages) | `session` | `users` → Eloquent `App\Models\User` |
| `api` | `jwt` | `users` |

`User` extends `Authenticatable` and implements `JWTSubject`.

Web market pages use middleware `user` (`AuthUserAccess`), then Spatie role groups. Admin pages use middleware `admin`. Details and flash messages are in `business-rules.md`.

JWT middleware `CheckJwt` runs before `jwt.auth` on the protected API group. It:

- Parses `Authorization` with `JWTAuth`.
- Returns JSON `errorType` `INVALID_TOKEN` or `TOKEN_EXPIRED`.
- On expiry, deletes `UserToken` rows for that user, `device-platform`, and `device-token`.
- Calls `User::checkAppVersion()` and can return that response before the controller.
- Requires a matching `user_login_tokens` row and refreshes `updated_at` unless the row is older than 4 hours, or 2 days when `keep_signedin` is set.
- Reads headers `device-platform`, `device-token`, and `app-version`. Empty device headers do not reject the call. The rejection message is commented out.

`GET /api/user` still uses `auth:api` and returns the user. It is separate from the `check-jwt` group.

These API routes have no JWT middleware: `login`, `forgot_password`, `send_test_notification/{id}`, `send_custom_notification`, `master_data`, `register`, `old_set_template`, several `special_donation_lot` reads, `contact_us`, and three download routes.

## Database

Connections in `config/database.php`:

| Name | Env | Notes |
|---|---|---|
| `mysql` | `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, `DB_PREFIX` | Default |
| `mysql2` | `DB_*_SECOND`, prefix `DB_PREFIX_SECOND` | `OnlineUser`, `UserHistory` |

Production `.env` (values that are not secrets):

| Key | Value |
|---|---|
| `APP_ENV` | `production` |
| `APP_DEBUG` | `false` |
| `APP_URL` | `https://market.hellochoice.co.za` |
| `DB_CONNECTION` | `mysql` |
| `DB_PORT` | `3306` |
| `DB_DATABASE` | `market_prod` |
| `DB_PREFIX` | `ft_` |
| `DB_DATABASE_SECOND` | `market_prod` |
| `DB_PREFIX_SECOND` | `ft_` |
| `CACHE_DRIVER` | `file` |
| `SESSION_DRIVER` | `file` |
| `SESSION_LIFETIME` | `120` |
| `QUEUE_DRIVER` | `sync` |
| `BROADCAST_DRIVER` | `pusher` |
| `MAIL_DRIVER` | `smtp` |
| `MAIL_HOST` | `mail.freshtrades.co.za` |
| `MAIL_PORT` | `465` |
| `MAIL_ENCRYPTION` | `tls` |
| `PUSHER_APP_CLUSTER` | `ap2` |
| `REDIS_HOST` | `127.0.0.1` |
| `REDIS_PORT` | `6379` |
| `LOG_PATH` | `/mnt/efs/www/html/market.hellochoice.co.za/storage/logs` |

The RDS hostname, usernames, passwords, `APP_KEY`, `JWT_SECRET`, `FCM_SERVER_KEY`, and Pusher secrets are in that file and are not copied here. `DB_HOST` and `DB_HOST_SECOND` are the same AWS RDS endpoint in `eu-west-1`.

`DEV.env` is a different shape: `APP_NAME=Laravel`, `APP_ENV=local`, `APP_URL=http://localhost`, database `bak_hellochoice`, `DB_PREFIX` value ` ft_` (leading space), `MAIL_MAILER`, `LOG_CHANNEL`, `QUEUE_CONNECTION`, `FILESYSTEM_DRIVER`, Mailhog, empty AWS bucket, region `us-east-1`. Those keys match a later Laravel skeleton more than Laravel 5.5's `config/mail.php` and `config/queue.php`, which this app still drives with `MAIL_DRIVER` and `QUEUE_DRIVER`.

238 migration files. The newest filenames are 2022-06-23 (transporters, routes, truck sizes, loading points). `DatabaseSeeder::run` has the user seeder call commented out. No other seeder classes are in `database/seeds`.

`Lot` and `BulkLot` use `SoftDeletes`.

## Background work

Registered in `Kernel::$commands` and also loaded by `$this->load` of the Commands directory.

Scheduled only when `APP_ENV` is not `dev`. Output files are under `LOG_PATH`. Each uses `withoutOverlapping()`.

| Command | Schedule | Also an open HTTP route |
|---|---|---|
| `UpdateLotStatus:update_lot_status` | every minute | `update_lot_status` |
| `ProxyBid:proxy_bid` | every minute | `start_proxy_bid` |
| `PriorProxyBid:prior_proxy_bid` | every minute | `prior_proxy_bid` |
| `SendMail:send_mail` | every minute | commented out |
| `sendNotification:send_notification` | every minute | `send_notifications` |
| `UpdateOfflineUser:update_offline_user` | every minute | `update_offline_user` |
| `BinQuantityRemove:remove` | every minute | `clear_cart` |
| `SendDailyBulletinMail:send_daily_bulletin_mail` | every minute | `send_daily_bulletin_mail` |
| `ReminderBinOfferQuantityMail:reminder_bin_offer_quantity_mail` | every minute | `reminder_bin_offer_quantity_mail` |
| `RemoveTempImages:remove_temp_images` | daily | `remove_temp_images` |
| `RemoveTempMyProducts:remove_temp_my_products` | daily | no |

Not scheduled, HTTP only: `UpdateBinLotStatus:update_bin_lot_status`, `DeleteLotBinCart:delete_lot_bin_cart`, `SoftClose:soft_close` (schedule line commented), `CreateMarketplaceFilterPdf:create_marketplace_filter_pdf` (schedule line commented).

Other open GET routes that call controller maintenance methods with no `user` middleware: `update_active_user_role`, `send_new_mail`, `removeLot`, `updateLotStatus`, `updateBulkLotStatus`, `testing`.

`QUEUE_DRIVER=sync`. There is a `Mailqueue` model. A separate queue worker is not configured in the production `.env`.

No cron file is in the repository. How the scheduler is started on the server is **Unknown**. The HTTP routes can run the same commands without a cron entry.

## Realtime and notifications

Events and listeners:

| Event | Listener |
|---|---|
| `LotUpdateFirebase` | `LotUpdateFirebaseListener` |
| `LotBidUpdateFirebase` | `LotBidUpdateFirebaseListener` |
| `LotBinQuantityUpdateFirebase` | `LotBinQuantityUpdateFirebaseListener` |
| `LotSpecialDonationStatusUpdateFirebase` | `LotSpecialDonationStatusUpdateFirebaseListener` |

Firebase paths when `APP_ENV` is not `local`: `/lots`, `/lot_bids`, `/users/`, `/bin_lots`, `/bin_lot_bids`, `/donation_special_lot`. Local prefix is `freshtrade/testing/...`.

FCM server key and sender id come from the environment. `config/fcm.php` is present. `app/Library/FirebaseFCM.php` is the app wrapper. `kreait/firebase-php` ^4 is also required.

Pusher is the production broadcast driver. Cluster `ap2`. Application event classes above are Firebase events, not Laravel broadcast events. Whether Pusher is used by a Blade script was not confirmed; `BROADCAST_DRIVER=pusher` is the configured value.

## Files and images

`config/filesystems.php` disks: `local` (`storage/app`), `public` (`storage/app/public`), `s3` (bucket `AWS_BUCKET`). Default cloud disk is `s3`. Production `.env` does not set `AWS_*` or `FILESYSTEM_CLOUD`.

Upload paths in `config/application.php` are relative folders under the public tree: `uploads/lots/`, `uploads/bulk_lots/`, `uploads/bin_bulk_lots/`, `uploads/special_donations/`, `uploads/product/`, `uploads/seller_document/`, `uploads/seller_logo/`, `uploads/notifications_attachments/`, `uploads/pdf/`, and others. `intervention/image` ^2.4 is required. `config/image.php` exists.

`public/uploads` is inside the 154,196-file `public` tree. Those blobs were not downloaded. Whether user uploads are committed is **likely** from the path layout and the file count, and was not confirmed file by file.

## Mail

Laravel mail driver in the production `.env` is SMTP to `mail.freshtrades.co.za` port 465, encryption `tls`. Username and password are committed and not copied.

`config/application.php` also sets `mailgun_domain` to `hellochoice.co.za` and a Mailgun API key in source. The key is not copied here. `mailgun/mailgun-php` is a Composer requirement. Which code path uses the SDK was not traced.

From-address in config is `noreply@hellochoice.net`, from name HelloChoice. Other operational addresses are listed in `prd.md`.

`config/mail_subjects.php` exists and was not line-reviewed.

## Deployment

`appspec.yml` version 0.0, OS linux:

- Source `/` to `/mnt/efs/www/html/market.hellochoice.co.za/`.
- Owner and group `abhishek`.
- `BeforeInstall`: `scripts/install_dependencies` as root, timeout 300. The `yum install httpd` line is commented out.
- `ApplicationStop`: `scripts/remove_archives.sh` as root. Deletes CodeDeploy revision directories other than the last successful install.
- `ApplicationStart`: `scripts/start_server` as root. The `service httpd start` line is commented out.

`scripts/stop_server` is not referenced by `appspec.yml`. It looks up `httpd` and does not stop it.

`deploy.txt` contains the single word `triggering`.

No Bitbucket Pipelines file is in git. No Docker file was in the root listing.

**Discrepancy:** root `.htaccess` is a cPanel block that selects `ea-php70`. The appspec destination is an EFS path and the hooks assume CodeDeploy on Linux. `public/web.config` is also in the tree (IIS). Which server actually serves the app is **Unknown**.

`.revision` holds a 40-character hex string that is not a commit in this repository.

## Configuration files

| File | Role |
|---|---|
| `config/application.php` | Domain rules, emails, Firebase, upload limits |
| `config/auth.php` | Session and JWT guards |
| `config/database.php` | `mysql` and `mysql2` |
| `config/fcm.php` | FCM package |
| `config/jwt.php` | JWT package |
| `config/permission.php` | Spatie |
| `config/excel.php` | Maatwebsite |
| `config/image.php` | Intervention |
| `config/mail.php`, `config/mail_subjects.php` | Mail |
| `config/services.php` | Third-party service stubs |
| `config/broadcasting.php`, `config/queue.php`, `config/cache.php`, `config/session.php` | Drivers |
| `config/filesystems.php` | local, public, s3 |
| `config/trustedproxy.php` | Proxy package `fideloper/proxy` |
| `config/assets.php` | `tooleks/laravel-asset-version` |
| `config/notification.php` | Present. Not line-reviewed |

`bcomposer.json` is a second manifest without `tymon/jwt-auth` and without `brozot/laravel-fcm`. `bvendor/` is committed beside `vendor/`.

## Security observations

These are facts about the tree, not a pentest.

1. A production `.env` is committed (`APP_ENV=production`) with database credentials, `APP_KEY`, JWT secret, FCM key, and Pusher secrets. `10c0b9fa` is "Restore prod .env".
2. `config/application.php` hardcodes a Google Maps key and a Mailgun API key.
3. `check.php` calls `phpinfo()`.
4. Unauthenticated routes execute Artisan commands and maintenance controller methods. Listed in the background-work section.
5. `GET testing` fires a Firebase event for lot id 1.
6. A commented route in `routes/web.php` contains an FCM device token. The token is not copied here.
7. Legal PDFs and `GET /get_all_market_lots`, `GET /bulletin/pdf`, bid-request accept/decline, and notification attachment download are outside the `user` middleware.
8. `GET` is used for bid placement, deletes, and offer decisions, so CSRF does not cover them.
9. API custom-notification and test-notification routes are outside `check-jwt`.
10. `.gitignore` is a generic template (node_modules, logs, IDE). It does not ignore `.env`, `vendor`, or `public/uploads`.

`.env` and `DEV.env` were read only to record non-secret keys and the values in the table above.

## Development and testing

`artisan` and `server.php` are present (Laravel's PHP built-in server front controller).

`webpack.mix.js` builds `resources/assets/js/app.js` to `public/js` and `resources/assets/sass/app.scss` to `public/css`. `app.js` mounts Vue on `#app` and registers `example-component` only.

`tests/Feature/ExampleTest.php` uses `RefreshDatabase` as an import and does not apply the trait. It asserts `GET /` returns 200. That route is behind `user` and redirects guests to login. `tests/Unit/ExampleTest.php` is the stock `assertTrue(true)` example. `tests/CreatesApplication.php` and `tests/TestCase.php` are the Laravel 5.5 base.

No CI configuration is in the repository. Sonar project files named in the onboarding task are not in git at `10c0b9fa`.

## Dependencies not used as the primary path

| Package or file | Observation |
|---|---|
| Redis settings | Present. Cache, session, and queue drivers are `file` / `sync` |
| S3 disk | Defined. Production `.env` has no AWS keys |
| `bvendor` / `bcomposer.json` | Older or narrower dependency set, still in the tree CodeDeploy copies |
| `Api\AuthControllerOld.php` | Not referenced by `routes/api.php` |
| Vue example component | Not referenced by the trading controllers that were read |

## What this pass did not establish

- Whether a cron process runs `schedule:run` on the EFS host.
- Whether `httpd` or another front controller is the live web server.
- The contents of `public/uploads` and `storage/`.
- Per-method validation inside every controller (rules classes exist under `app/Rules/` and were not each line-reviewed).
- Which mail messages use Mailgun and which use SMTP.
- The pre-2023 git history. This remote's first commit is 2023-08-11.
