|
| 1 | +# Cloudlog AI Coding Agent Instructions |
| 2 | + |
| 3 | +## Project Overview |
| 4 | +Cloudlog is a self-hosted **PHP web application** for amateur radio contact logging. Built on **CodeIgniter 3** MVC framework with Bootstrap 5 frontend, HTMX for AJAX interactions, and jQuery for enhanced functionality. |
| 5 | + |
| 6 | +**Stack**: PHP 7.4+ (8.2 compatible), MySQL 5.7+, Apache/Nginx, CodeIgniter 3 |
| 7 | + |
| 8 | +UI: Default Bootstrap styling (no custom theme by default). |
| 9 | + |
| 10 | +## Architecture |
| 11 | + |
| 12 | +### MVC Structure (CodeIgniter 3) |
| 13 | +- **Controllers** (`application/controllers/`): Extend `CI_Controller`, handle authentication via `user_model->validate_session()` |
| 14 | +- **Models** (`application/models/`): Extend `CI_Model`, handle database operations (e.g., `Logbook_model`, `Stations`) |
| 15 | +- **Views** (`application/views/`): PHP templates with Bootstrap 5 classes, loaded via `$this->load->view()` |
| 16 | +- **Libraries** (`application/libraries/`): Custom classes like `Qra` (gridsquare calculations), `optionslib` (settings) |
| 17 | + |
| 18 | +### Key Patterns |
| 19 | +```php |
| 20 | +// Controllers always check authentication first |
| 21 | +$this->load->model('user_model'); |
| 22 | +if ($this->user_model->validate_session() == 0) { |
| 23 | + redirect('user/login'); |
| 24 | +} |
| 25 | + |
| 26 | +// Authorization levels: authorize(2) for users, authorize(99) for admins |
| 27 | +if (!$this->user_model->authorize(2)) { |
| 28 | + $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); |
| 29 | + redirect('dashboard'); |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +### Configuration |
| 34 | +- **Config files**: `application/config/config.php` (app settings), `database.php` (DB credentials) |
| 35 | +- Sample files: `config.sample.php`, `database.sample.php` - copy and customize for local setup |
| 36 | +- Environment: Set in `index.php` - `define('ENVIRONMENT', 'development')` shows profiler |
| 37 | +- Version: Defined in `config.php` as `$config['app_version'] = "2.4.5"` |
| 38 | + |
| 39 | +## Frontend Integration |
| 40 | + |
| 41 | +### HTMX Usage (Primary AJAX Method) |
| 42 | +HTMX is the **preferred** method for dynamic content loading. Views use `hx-get`, `hx-post`, `hx-target` attributes: |
| 43 | +```php |
| 44 | +<!-- Auto-refreshing component --> |
| 45 | +<div id="qso-last-table" hx-get="<?php echo site_url('/qso/component_past_contacts'); ?>" |
| 46 | + hx-trigger="load, every 5s"> |
| 47 | +</div> |
| 48 | + |
| 49 | +<!-- Form submission --> |
| 50 | +<form hx-post="<?php echo site_url('logbooks/save_publicslug/'); ?>" |
| 51 | + hx-target="#publicSlugForm"> |
| 52 | +``` |
| 53 | + |
| 54 | +### HTMX In Practice |
| 55 | +- Previous QSOs widget: |
| 56 | + - View snippet (auto-refresh): |
| 57 | + ```php |
| 58 | + <div id="qso-last-table" hx-get="<?php echo site_url('/qso/component_past_contacts'); ?>" hx-trigger="load, every 5s"></div> |
| 59 | + ``` |
| 60 | + - Controller endpoint: `application/controllers/Qso.php::component_past_contacts()` loads `application/views/qso/components/previous_contacts.php` with `$this->logbook_model->last_custom('5')`. |
| 61 | + |
| 62 | +- Save Public Slug form: |
| 63 | + - View snippet: |
| 64 | + ```php |
| 65 | + <form hx-post="<?php echo site_url('logbooks/save_publicslug/'); ?>" hx-target="#publicSlugForm"> |
| 66 | + <input type="hidden" name="logbook_id" value="<?php echo $logbook_id; ?>"> |
| 67 | + <input type="text" name="public_slug" required> |
| 68 | + <button type="submit" class="btn btn-primary">Save</button> |
| 69 | + </form> |
| 70 | + ``` |
| 71 | + - Controller endpoint: `application/controllers/Logbooks.php::save_publicslug()` validates `public_slug` (`required|alpha_numeric`) and persists via `logbooks_model`. |
| 72 | + |
| 73 | +### Assets |
| 74 | +- **Most assets**: Live under `assets/`; core includes are wired via `application/views/interface_assets/header.php` and `application/views/interface_assets/footer.php`. |
| 75 | +- **CSS**: `assets/css/` - Bootstrap themes, custom overrides in `themes/*/overrides.css` |
| 76 | +- **JS**: `assets/js/` - jQuery, HTMX (`htmx.min.js`), Leaflet maps, custom logic |
| 77 | +- **Icons**: Font Awesome (via `assets/fontawesome/`) |
| 78 | + |
| 79 | +## Database |
| 80 | + |
| 81 | +### Migrations |
| 82 | +Database schema managed via **CodeIgniter migrations** (`application/migrations/`): |
| 83 | +- Sequential numbered files: `001_add_lotw_credentials.php` → `232_tag_2_7_7.php` |
| 84 | +- Each extends `CI_Migration` with `up()` method |
| 85 | +- Run via: `php index.php migrate` or through admin interface |
| 86 | + |
| 87 | +### Key Tables |
| 88 | +- `TABLE_HRD_CONTACTS_V01`: QSO log (configured in `config.php`) |
| 89 | +- `station_profile`: Station locations and settings |
| 90 | +- `station_logbooks`: Logbook definitions |
| 91 | +- `station_logbooks_entity`: Logbook-location relationships |
| 92 | + |
| 93 | +## Development Workflows |
| 94 | + |
| 95 | +### Docker Development Setup |
| 96 | +```bash |
| 97 | +# Start environment (web + db services) |
| 98 | +docker-compose up |
| 99 | + |
| 100 | +# Access: http://localhost/ |
| 101 | +# DB host in Docker: 'db' (service name, not localhost) |
| 102 | +``` |
| 103 | + |
| 104 | +**Configuration**: Copy `.env.sample` to `.env` and adjust DB settings before starting. |
| 105 | + |
| 106 | +### Testing |
| 107 | +**Cypress** end-to-end tests (`cypress/e2e/`): |
| 108 | +```bash |
| 109 | +# Install & run tests |
| 110 | +npm install cypress |
| 111 | +npx cypress run |
| 112 | + |
| 113 | +# Tests require Docker containers running |
| 114 | +docker-compose up -d |
| 115 | +``` |
| 116 | + |
| 117 | +Tests validate: login flows, station creation, logbook operations, version checks. |
| 118 | + |
| 119 | +### Common Tasks |
| 120 | +- **Enable profiler**: Set `ENVIRONMENT = 'development'` in `index.php` |
| 121 | +- **Routing**: CI3 maps `/controller/method` to `Controller::method` by default; `application/config/routes.php` is typically left at its default (`default_controller = 'dashboard'`). Only edit routes for custom remaps. |
| 122 | +- **Base URL helpers**: Use `site_url()` and `base_url()` in views/controllers |
| 123 | + |
| 124 | +## Where To Start (New Features) |
| 125 | +- **Controller**: Add `application/controllers/MyFeature.php` extending `CI_Controller`. In `__construct()` or method start, load `user_model` and enforce `validate_session()`/`authorize()` as needed. Return views via `$this->load->view()`. |
| 126 | +- **Model**: Add `application/models/Myfeature_model.php` for DB access. Use CI Query Builder. Inject via `$this->load->model('myfeature_model')`. |
| 127 | +- **View**: Create `application/views/myfeature/*.php`. Include header/footer (`interface_assets/header` and `interface_assets/footer`). Prefer HTMX (`hx-get`/`hx-post`) for async UI. |
| 128 | +- **Migrations**: For schema changes, add `application/migrations/NNN_description.php` and run `php index.php migrate` (or use the admin UI). Keep IDs sequential. |
| 129 | +- **Routing note**: New endpoints are reachable as `/index.php/myfeature/method` (and usually `/myfeature/method` with proper web server config); no routes entry required unless you need a custom URI. |
| 130 | + |
| 131 | +## Domain-Specific Context |
| 132 | + |
| 133 | +### Amateur Radio Concepts |
| 134 | +- **Gridsquare/Locator**: Maidenhead grid system for location (e.g., `IO87JP`) |
| 135 | +- **QSO**: Radio contact/log entry |
| 136 | +- **DXCC**: Country entities for award tracking |
| 137 | +- **ADIF**: Amateur Data Interchange Format for import/export |
| 138 | +- **LoTW/eQSL**: Electronic QSL card confirmation systems |
| 139 | + |
| 140 | +### Libraries & Helpers |
| 141 | +- **Qra library** (`application/libraries/Qra.php`): Calculate bearings, distances, gridsquare conversions |
| 142 | +- **qra2latlong()**: Global function (defined in `Qra.php`) converts Maidenhead to coordinates |
| 143 | + |
| 144 | +## Code Conventions |
| 145 | + |
| 146 | +### Controllers |
| 147 | +- Load models in `__construct()` or method start |
| 148 | +- Use flashdata for user messages: `$this->session->set_flashdata('notice', 'Message')` |
| 149 | +- Redirect after POST: `redirect('controller/method')` |
| 150 | + |
| 151 | +### Views |
| 152 | +- Header/footer: `$this->load->view('interface_assets/header', $data)` + `footer.php` |
| 153 | +- JavaScript globals defined in `footer.php`: `base_url`, `site_url`, `my_call` |
| 154 | +- Language strings: `<?php echo lang('key'); ?>` (files in `application/language/`) |
| 155 | + |
| 156 | +### Security |
| 157 | +- Input sanitization: `$this->security->xss_clean($input)` |
| 158 | +- Prevent direct access: `if (!defined('BASEPATH')) exit('No direct script access allowed');` |
| 159 | +- SQL: Use Query Builder or prepared statements (handled by CI3 models) |
| 160 | + |
| 161 | +## Pull Request Guidelines |
| 162 | +- **Target branch**: `dev` (PRs to `main` will be rejected) |
| 163 | +- **One feature per PR**: No multi-feature or bundled bug fixes |
| 164 | +- **Comment code**: Explain non-obvious logic |
| 165 | +- **Test coverage**: Run Cypress tests before submitting |
| 166 | +- **Description**: Clearly state what the PR does and why it's needed |
| 167 | + |
| 168 | +## Common Pitfalls |
| 169 | +- **Don't use `&&` in PowerShell commands** - use `;` to chain commands |
| 170 | +- **Docker DB host**: Use service name `db`, not `localhost` or `127.0.0.1` |
| 171 | +- **Config files**: Never commit `config.php` or `database.php` (use `.sample` versions as templates) |
| 172 | +- **Base URL**: Must be set correctly in `config.php` for site to work properly |
0 commit comments