# Technical documentation — findndrive_webportal

Evidence date: 2026-10-15. Source: `git@bitbucket.org:overdrive/findndrive_webportal.git`, `master` at `54b9de7` (2023-10-12, "database settings"). Secret values are omitted.

## Stack

| Item | Value | Evidence |
|---|---|---|
| Language | PHP `^8.0` | `composer.json`, `README.md` |
| Framework | CodeIgniter `v4.3.3` | `composer.lock` |
| Package name | `codeigniter4/adminstarter` | `composer.json` |
| Description | Find And Drive | `composer.json` |
| Database | MySQLi, charset `utf8`. PHPUnit uses SQLite `:memory:` with prefix `db_` | `app/Config/Database.php` |
| Front controller | `public/` | README virtual-host example |
| Auto-routing | `$routes->setAutoRoute(true)` | `app/Config/Routes.php` |
| Default controller | Explicit route `/` → `Welcome::index`. Default controller name is still `Home` | `Routes.php` |

## Repository layout

```text
app/                 controllers, models, views, filters, helpers, libraries, migrations, seeds
public/              web root
writable/            cache, logs, session, uploads (index.html placeholders)
vendor/              committed dependencies, including the framework
tests/               CodeIgniter example tests
scripts/             CodeDeploy hooks
builds/              present at the top level recorded during discovery
env                  sample environment file (not named .env)
config.php           Salesforce Marketing Cloud client settings
appspec.yml          CodeDeploy spec
bitbucket-pipelines.yml
codedeploy_deploy.py
composer.json / composer.lock
phpunit.xml.dist
sonar-project.properties
spark                CI CLI
```

`vendor` is in the tree that the pipeline zips (`zip -r /tmp/artifact.zip *`). ApplicationStart also runs `composer update --no-dev`.

## Application structure

Controllers in `app/Controllers`:

| Class | Extends | Notes |
|---|---|---|
| `Welcome`, `Home`, `Users`, `Car`, `UploadedFile`, `MediaFile`, `MasterFile`, `ChatGPT`, `SiteHealth`, `Test`, `TestLog`, `DbAdmin` | `BaseController` | Staff UI. Helpers loaded from `BaseController` |
| `CarApi`, `Organisations`, `ApiSimulation` | `ResourceController` | JSON. They construct `JwtLibrary` themselves |
| `Developers\MenuManagement`, `CRUDGenerator`, `DbTableDataView` | `BaseController` | Developer tools |

`BaseController` loads helpers `cookie`, `date`, `security`, `menu`, `useraccess`, `africastalking`, `salesforce`, `mail`, `chatgpt`, `aws`, and constructs user, menu, car, master-file, uploaded-file, and app-session models.

Models use the query builder rather than CI model `$allowedFields` for most writes. Tables touched include `car`, `car_cost_category_a`, `car_cost_category_b`, `car_cost_category_c`, `app_session`, `users`, `user_role`, `user_menu`, `user_access`, `organisations`, `uploaded_file`, `chatgpt_log`.

## Routing

Explicit routes are listed in `app/Config/Routes.php`. Because auto-routing is on, any public controller method is also reachable as `/{controller}/{method}`.

REST:

- `$routes->resource('carapi')` → `CarApi` index, show, create, update, delete.
- `organisations` is not registered with `resource()`. The comment block describes REST verbs. With auto-route, `organisations`, `organisations/show`, and `organisations/login` hit that controller. Whether `PUT organisations/1` maps cleanly is **Requires Confirmation**: ResourceController routing was not declared for this class.

Filters (`app/Config/Filters.php`):

| Alias | Class | Global |
|---|---|---|
| `cors` | `App\Filters\Cors` | before, every request |
| `isLoggedIn` | `App\Filters\Authentication` | before, except `/`, `getLogin`, `register`, `organisations`, `organisations/*`, `carapi`, `carapi/*` |
| `isGranted` | `App\Filters\Authorization` | before, except the login set plus `blocked`, `home`, `Welcome/*`, `chatGPT/*` |
| `toolbar` | CI debug toolbar | after, except `chatGPT/*` and `carapi` |
| `csrf`, `honeypot` | CI | defined, not global |

`App::$baseURL` is `''`. `indexPage` is `index.php`. README shows a WAMP vhost `adminOverdrive.local` with document root `public`. Production document root from CodeDeploy is `/var/www/html/`, which is the repository root, not `public/`. **Requires Confirmation** how Apache on the instance maps the site.

## Data

Migrations live in `app/Database/Migrations` and run with `php spark migrate` from `scripts/start_server`.

Core tables created by those migrations:

| Table | Role |
|---|---|
| `users`, `user_role`, `user_access`, `user_menu`, `user_submenu`, `user_menu_category` | Staff identity and menus |
| `organisations` | API tenants |
| `car`, `car_type`, `car_tier`, `car_service` | Catalogue |
| `car_cost_category_a`, `car_cost_category_b`, `car_cost_category_c` | Service parts, wear parts, body parts |
| `app_session` | One simulator run. Columns grow across later migrations (loan fields, salary, expenses, geo, tier, recommendation lists) |
| `app_session_log` | Created. No writer was found under `app/` besides the migration |
| `uploaded_file`, `media_file` | File registry |
| `master_file`, master-file model table, overland table | Master-file feature. Column set includes taste notes and history |
| `test`, `test_log` | Side-file projects |
| `chatgpt_log` | Written by `gptLog` |
| `settings`, `language` | Created. No product reader was traced |
| `cronjob_actions`, `cronjob_log` | Created. No runner |
| CI `sessions` table | Session migration `2022-11-20-124435_Session.php` |

`app_session` monetary and measurement columns added for the API are `VARCHAR`, including `interest_rate`, `net_salary`, and `est_repayment`.

Seeds (`php spark db:seed users` in the README; the deploy hook has this line commented):

- User `jaco@overdrive.co.za`, role Developer.
- Menus: Dashboard, Users, Menu Management, CRUD Generator, Car Database, Data Files, Media Files.
- Organisation `silicon overdrive`.
- Car types Sedan, SUV, Hatchback, Coupe.
- An uploaded-file seed row.

Seed passwords and api keys are not copied here.

## Configuration

| File | What it sets |
|---|---|
| `env` | `CI_ENVIRONMENT = development`. Session database handler. `database.default.database = adminoverdrive`, username `root`, empty password. An `encryption.key` hex value is committed. The value is omitted |
| `app/Config/Database.php` | Default group hostname `localhost`, port 3306, empty username, password, and database. Commented `getSecret` calls name an RDS secret id. The id is omitted |
| `app/Config/App.php` | Empty `baseURL` |
| `config.php` (repository root) | Salesforce Marketing Cloud `clientid`, `clientsecret`, `appsignature` `none`, WSDL host `buildingtomorrow.soap.marketingcloudapis.com`. Secret values omitted |
| `sonar-project.properties` | Project key `overdrive_findndrive_webportal_AYlFfmWqp5diRRkAyQho` |

The last commit message is "database settings", which matches the emptied `Database.php` credentials.

## Authentication

Two mechanisms:

1. Staff: PHP session after `password_verify`. See `Welcome` and `Authentication`.
2. API: custom JWT in `app/Libraries/JwtLibrary.php`. HMAC-SHA256. Default secret is a short literal in the method signature. Header read from `Authorization`, `HTTP_AUTHORIZATION`, or `apache_request_headers`. Bearer regex `Bearer\s(\S+)`. Expiry is required; a token without `exp` will error when `exp` is read.

Simulator steps do not use mechanism 2. See the PRD.

## Integrations

| Integration | Code | Active? |
|---|---|---|
| OpenAI `POST /v1/completions`, model `text-davinci-003`, temperature 0.7, max_tokens 256 | `chatgpt_helper.php` | Yes, from `ChatGPT::sent` and `MasterFile` processors. SSL verify peer and host are 0. Bearer token is hardcoded |
| AWS Secrets Manager, region `us-east-1`, profile `default` | `aws_helper.php` | Helper exists. Database config does not call it |
| AWS CodeDeploy | `bitbucket-pipelines.yml`, `codedeploy_deploy.py`, `appspec.yml` | Pipeline definition on branch `master` |
| Salesforce Fuel SDK `ET_Client` / `ET_List` | `salesforce_helper.php`, `config.php` | Helper exists. Call sites found are commented out |
| Africa's Talking SMS, username `sandbox` | `africastalking_helper.php` | Helper exists. Call sites found are commented out. API key and recipient are hardcoded |
| CI email, from `php@overdrive.co.za` | `mail_helper.php` | Helper exists. Call sites found are commented out |
| PhpSpreadsheet | `Car::importSm`, `MasterFile` imports | Yes |
| mPDF, `orhanerday/open-ai`, smtp2go | `composer.json` | Declared. No `app/` caller was found for mPDF, the OpenAI package, or the SMTP2GO client |

Pipeline variables named in the file: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`, `APPLICATION_NAME`, `DEPLOYMENT_GROUP_NAME`, `S3_BUCKET`, `DEPLOYMENT_CONFIG`, `SONAR_HOST_URL`, `SONAR_TOKEN`. Values are not in the repo.

`boto3==1.3.0` is installed in the pipeline image `python:3.5.7`.

## Deployment

`appspec.yml`:

- OS linux, copy `/` to `/var/www/html/`, owner `www-data`, `file_exists_behavior: OVERWRITE`.
- BeforeInstall: `scripts/install_dependencies` (the file only contains a commented `yum install httpd`).
- ApplicationStop: `scripts/remove_archives.sh`.
- ApplicationStart: `scripts/start_server`.

`start_server`:

- `cd /var/www/html/`
- `composer update --no-dev`
- `chown -R www-data:www-data /var/www`
- Attempts to start httpd or apache2. The tests are written `isExistApp = \`pgrep …\`` with spaces around `=`, which is not valid Bash assignment.
- `php spark migrate`
- users seed is commented out.

Pipeline order on `master`: deploy step, then the SonarQube step. Sonar does not gate the deploy; it runs after the deploy step in the same pipeline list.

## Security notes (no secret values)

These are observations for human review, not a pentest.

- Credentials and tokens are committed in `chatgpt_helper.php`, `africastalking_helper.php`, `config.php`, `env` (`encryption.key`), `CarApi::step0_login`, and database seeds.
- JWT HMAC key is a code default.
- Simulator steps skip JWT verification. `CarApi::index` returns every session.
- CORS origin is `*`. CSRF is off.
- Registration creates a Developer.
- `CarModel` type filters concatenate request text into SQL.
- ChatGPT cURL disables certificate verification.
- `Database.php` in git has empty credentials, so a deploy that does not provide `.env` or another override has no database login.

## Background work

No queue worker, no `app/Commands`, no cron entrypoint. Tables `cronjob_actions` and `cronjob_log` are unused by application code found in this pass.

`MasterFile::processTasteNotes` and `processHistory` loop up to 100 rows synchronously with `max_execution_time` 6000. They are HTTP actions, not jobs.

## Development and tests

README local setup:

1. Copy `env` to `.env`.
2. `php spark db:create`
3. `php spark migrate`
4. `php spark db:seed users`
5. `php spark key:generate`
6. `php spark serve`

`tests/` contains the framework samples (`HealthTest`, `ExampleSessionTest`, `ExampleDatabaseTest`) and support fixtures. No test references `CarApi`, `Car`, or `Organisations`.

`composer test` runs PHPUnit. `phpunit.xml.dist` is present. This onboarding did not execute the suite.

## Monitoring

SonarQube is the only monitoring hook in-repo. Application logging uses CodeIgniter's logger directory `writable/logs`. No product metrics exporter was found.

Dashboard charts are an in-app view of `app_session`, not an external monitor.

## Related documents that were not read

`README.md` links:

- Jira `https://siliconsoftware.atlassian.net/jira/software/projects/SI/boards/12`
- Confluence `https://siliconsoftware.atlassian.net/wiki/spaces/AO/overview`

Their contents are **Unknown**. They may describe Admin Overdrive rather than Find And Drive. **Requires Confirmation**.
