# Technical documentation — webportal (Aloecate)

Evidence date: 2026-09-28. Source: `master` at `0cd0456`.

## Stack

| Item | Evidence |
|---|---|
| PHP | `index.php` CodeIgniter front controller. Runtime version on the server is **Unknown** |
| CodeIgniter | `system/core/CodeIgniter.php` constant `CI_VERSION = '3.1.11'` |
| MySQL via `mysqli` | `application/config/database.php` |
| Composer | `aws/aws-sdk-php` `^3.212` only. `vendor/` is in git |
| UI | AdminLTE assets under `assets/`, Bootstrap 4, jQuery, DataTables |
| HTTP API | Chris Kacerguis REST controller pattern: `application/libraries/REST_Controller.php`, config `application/config/rest.php` |

`index.php` sets `ENVIRONMENT` from `CI_ENV`, default `production`.

## Repository layout

```text
index.php                 front controller
appspec.yml               CodeDeploy file mapping
bitbucket-pipelines.yml   CI deploy
composer.json             AWS SDK
application/              controllers, models, views, config, helpers, libraries, core
assets/                   AdminLTE and plugins
db/                       SQL scripts and a 2022-06-17 dump
system/                   CodeIgniter 3 core
vendor/                   committed AWS SDK
uploads/                  includes script.bat
```

Not present on `master`: README, PHPUnit, Docker, `sonar-project.properties`, `sonarqube_scan.bat`. The onboarding scan listed the two Sonar files. They are not in git.

## Request flow

1. `index.php` boots CodeIgniter.
2. `application/config/routes.php` sends `/` to `dashboard`. Other URLs use the default `/controller/method/id` pattern. `translate_uri_dashes` is false.
3. Autoload (`application/config/autoload.php`): libraries `session`, `database`, `parser`, `form_validation`, `uploadlib`, `email`. Helpers `basic`, `url`, `file`, `form`, `cookie`, `security`, `directory`, `language`, `aws`. Models listed in that file include users, settings, roles, permissions, activity, templates, items, both user models, files, cash-out, jobs, messages, cron, engine, and transactions.
4. Staff controllers extend `MY_Controller`, which checks the database config, sets timezone from settings, loads language `basic`, and redirects anonymous users to `login`.
5. `Api` extends `REST_Controller` and does not use the staff session.

Hooks file `application/config/hooks.php` has no enabled hooks.

## Configuration

| Setting | Where | Value found |
|---|---|---|
| Base URL | `application/config/config.php` | `https://webportal.aloecate.co.za/` |
| Database | `database.php` via `get_secret('db_password', ...)` | host, username, password, dbName. Region `eu-west-1` |
| REST format | `rest.php` | JSON. `force_https` false. `rest_auth` false. `rest_enable_keys` false |
| Email | `email.php` | SMTP, `smtp.example.com`, port 465, ssl. Placeholder password. Not repeated |
| CSRF meta | `views/includes/header.php` | Token name and hash are rendered for scripts |

`get_secret` uses the AWS SDK Secrets Manager client and the default credential provider. On failure it returns nothing useful to the page beyond the SDK exception path. Exact user-facing text for a missing secret was not executed.

## Data model

Database name in the SQL files: `aloecate`.

| Table | Role | Defined in |
|---|---|---|
| `users` | Staff accounts. Password is SHA-256 text. `status`, `role`, `reset_token` | `db/starter.sql`, dump |
| `roles`, `permissions`, `role_permissions` | Staff access. Role titles in the dump: Admin, Manager, User, App-User | dump |
| `settings` | Key/value. Seed includes company_name AdminPRO, timezone Asia/Kolkata, reCAPTCHA flags | `db/starter.sql` |
| `email_templates` | Reset template code `reset_password` | starter and dump |
| `app_user` | Mobile user: name, surname, cellnumber, email, password, location, bank_name, bank_acc_holder, bank_acc_number, terms_accepted, code, verified, balance, referred_code, referral_code, processed, role default 4 | dump |
| `app_user_jobs` | employer_name, reference_name, reference_telno, date_start, date_end | dump |
| `file` | Slip image key, your_slip, clear_slip, type, processed, rebate, app_user_id | dump |
| `aloecate_items` | filename, product, totals, amounts, rebate, file_id, processed | dump |
| `aloecate_engine_rules` | product, location, date_from, date_to, rebate_amount, status | `db/update.4.liam.sql`, `update.5.liam.sql` |
| `cashout` | cashout_amount, app_user_id, processed, user_processed_id. Later columns `paid_out_amount`, `process_date` | dump, `db/update.1.liam.sql` |
| `transactions` | app_user_id, transaction_type, amount, balance_before, balance_after, transaction_date | `db/update.5.liam.sql` |
| `messages` | Used by `Messages_model`. `is_backend_user` added in `db/update.2.liam.sql`. Full `CREATE TABLE` was not found | model |
| `activity_logs` | title, user, ip, timestamps. `type` added in `db/update.3.liam.sql` | starter, update |
| `keys`, `logs` | REST library tables. Key auth is off | dump |
| `job`, `reciept_details`, `user_reciept`, `file_old` | Present in the dump. No references under `application/` | dump |

The dump file also contains row data. It is not a schema-only script. Do not publish it. Personal data is not copied into this document.

There is no migration runner. Scripts are loose files: `starter.sql`, `main.sql` (same starter shape), `update.1` through `update.5` attributed to Liam in the filename, and `FullDBdump17062022.sql`. Order of application on a fresh database is **Unknown / Requires Confirmation**. `starter.sql` ends with a stray token `aloecate` after an `AUTO_INCREMENT` clause.

Engines mix InnoDB (users, app_user, file, cashout) and MyISAM (`aloecate_items`, `aloecate_engine_rules`, `transactions`, `reciept_details`).

## API

Controller: `application/controllers/Api.php`.

Conventions: REST method suffix `_get` / `_post`. Id is URI segment 3. Responses are JSON. CORS header `*` is set in the constructor. Upload limits are raised to 64M in the constructor and again in `submit_slip_post`.

`application/controllers/Rest.php` is a separate test controller.

Authentication: none in config. See business rule 12.

## AWS

| Call | Function | Notes |
|---|---|---|
| Put slip from `$_FILES` | `aws_put_file` | Default region `eu-west-1`. Default credential chain. Bucket argument from the caller: `aloecate-staging-uploads` |
| Put raw bytes | `aws_put_file_data` | Uses secret `s3_textract`. Content type `image/png` |
| Presign GET | `aws_url_signed` | 10 minutes |
| Textract local file | `aws_textract` | `AnalyzeDocument`, FORMS and TABLES, API version `2018-06-27` |
| Textract S3 object | `aws_textract_s3`, `aws_textract_s3_process` | Same client. `files/process` uses `aws_textract_s3_process` |

S3 client in `aws_put_file` will try `createBucket` if `doesBucketExist` is false.

Secret ids referenced in code: `db_password`, `s3_textract`. Comment-only names: `smtp_pass`, `access_key`, `secret_key`, and several `xero_*` parameter-store keys. No Xero SDK calls were found.

## SMS

BulkSMS JSON API. Send is HTTP on port 80 to `/v1/messages` with `auto-unicode=true&longMessageMaxParts=30`. Balance is HTTPS `/v1/profile` with SSL verify host and peer turned off. Credentials are literals in PHP. Omitted here.

Timeouts: 20 seconds overall, 10 seconds connect.

## Deployment

`bitbucket-pipelines.yml` image `atlassian/default-image:2`.

- Branch `master`: zip the working tree to `application1.zip`, CodeDeploy pipe `atlassian/aws-code-deploy:0.2.5` command `upload` then `deploy`. Variables: `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `APPLICATION_NAME`, `S3_BUCKET`, `DEPLOYMENT_GROUP`. `WAIT` true. `IGNORE_APPLICATION_STOP_FAILURES` true. `FILE_EXISTS_BEHAVIOR` `OVERWRITE`. `VERSION_LABEL` literal `my-app-1.0.0`.
- Branch `staging`: same steps, but access key, secret, application name, and bucket variables use the `PROD` / `PRODUCTION` names. Deployment group variable is still `DEPLOYMENT_GROUP`.

`appspec.yml` version 0.0, OS linux, source `/` to `/var/www/html/webportal`, owner and group `www-data`, mode 755. No hook scripts are defined, so CodeDeploy does not run a restart command from this file.

The zip includes `vendor/`, `system/`, and `db/` because the script zips `.`.

No test step runs in the pipeline.

## Background work

`Cron_model` can insert a row and return the last `created_at`. The dashboard shows that timestamp. No CLI controller, crontab file, or queue worker is in the repository. What writes the cron row is **Unknown / Requires Confirmation**.

`Activity_model` is synchronous on the request that calls it. SMS send is synchronous inside the staff save request.

## Security notes (observation, not a procedure)

- API is unauthenticated and lists users.
- App-user passwords are stored as submitted.
- Staff passwords are unsalted SHA-256.
- Item search concatenates `date_from` and `date_to` into SQL.
- SMS and some seed secrets are in git. The June 2022 dump contains personal data.
- `sms_balance` disables TLS certificate checks.
- `email.php` contains a placeholder secret.

Values of those secrets are intentionally absent from Silicon Ark.

## Development and testing

No contributor guide. No automated tests. Local run instructions are not in the repo. A developer would need PHP capable of CodeIgniter 3, the mysqli extension, AWS credentials that can read the two secrets, and a MySQL database loaded from the SQL files. That last sentence is **Inferred** from the stack, not from a README.

`sonarqube_scan.bat` is not in the tree, so the scan command the filename would have contained is **Unknown**.

## Monitoring

No application log aggregator, health check, or metrics client is configured in the files read. CodeIgniter's own logs follow the framework default. The log threshold in `config.php` was not extracted. **Unknown** whether CloudWatch or similar is attached on the EC2 host.
