- Add audit-logging-plan.md with comprehensive logging implementation guide - Update AGENTS.md with project guidelines - Refactor test models: remove RefTHoldModel, RefVSetModel, TestDefTechModel - Update TestDefSiteModel and related migrations - Update seeder and test data files - Update API documentation (OpenAPI specs)
7.4 KiB
7.4 KiB
AGENTS.md - Code Guidelines for CLQMS
CLQMS (Clinical Laboratory Quality Management System) - A headless REST API backend for clinical laboratory workflows built with CodeIgniter 4.
Build, Test & Lint Commands
Running Tests
# Run all tests
./vendor/bin/phpunit
# Run a specific test file
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run a specific test method
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
# Run tests with coverage
./vendor/bin/phpunit --coverage-html build/logs/html
# Run tests by suite
./vendor/bin/phpunit --testsuite App
CodeIgniter CLI Commands
# Run spark commands
php spark <command>
# Generate migration
php spark make:migration <name>
# Generate model
php spark make:model <name>
# Generate controller
php spark make:controller <name>
# Generate seeder
php spark make:seeder <name>
# Run migrations
php spark migrate
# Rollback migrations
php spark migrate:rollback
Composer Commands
# Install dependencies
composer install
# Run tests via composer
composer test
# Update autoloader
composer dump-autoload
Code Style Guidelines
PHP Standards
- PHP Version: 8.1+
- PSR-4 Autoloading:
App\maps toapp/,Config\maps toapp/Config/ - PSR-12 Coding Style (follow where applicable)
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | PatientController |
| Methods | camelCase | createPatient() |
| Properties | snake_case (legacy) / camelCase (new) | $patient_id / $patientId |
| Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
| Tables | snake_case | patient_visits |
| Columns | PascalCase (legacy) | PatientID, NameFirst |
| JSON fields | PascalCase | "PatientID": "123" |
File Organization
app/
├── Config/ # Configuration files
├── Controllers/ # API controllers (grouped by feature)
│ ├── Patient/
│ ├── Organization/
│ └── Specimen/
├── Models/ # Data models
├── Filters/ # Request filters (Auth, CORS)
├── Traits/ # Reusable traits
├── Libraries/ # Custom libraries
├── Helpers/ # Helper functions
└── Database/
├── Migrations/
└── Seeds/
Imports & Namespaces
- Always use fully qualified namespaces at the top
- Group imports: Framework first, then App, then external
- Use statements must be in alphabetical order within groups
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\ResponseInterface;
use App\Traits\ResponseTrait;
use Firebase\JWT\JWT;
Controller Structure
<?php
namespace App\Controllers;
use App\Traits\ResponseTrait;
class ExampleController extends BaseController
{
use ResponseTrait;
protected $model;
public function __construct()
{
$this->model = new \App\Models\ExampleModel();
}
// GET /example
public function index()
{
// Implementation
}
// POST /example
public function create()
{
// Implementation
}
}
Response Format
All API responses must use the standardized format:
// Success response
return $this->respond([
'status' => 'success',
'message' => 'Operation completed',
'data' => $data
], 200);
// Error response
return $this->respond([
'status' => 'failed',
'message' => 'Error description',
'data' => []
], 400);
Error Handling
- Use try-catch for JWT operations and external calls
- Return structured error responses with appropriate HTTP status codes
- Log errors using CodeIgniter's logging:
log_message('error', $message)
try {
$decoded = JWT::decode($token, new Key($key, 'HS256'));
} catch (\Firebase\JWT\ExpiredException $e) {
return $this->respond(['status' => 'failed', 'message' => 'Token expired'], 401);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'message' => 'Invalid token'], 401);
}
Database Operations
- Use CodeIgniter's Query Builder or Model methods
- Prefer parameterized queries over raw SQL
- Use transactions for multi-table operations
$this->db->transStart();
// ... database operations
$this->db->transComplete();
if ($this->db->transStatus() === false) {
return $this->respond(['status' => 'error', 'message' => 'Transaction failed'], 500);
}
Testing Guidelines
Test Structure
<?php
namespace Tests\Feature\Patients;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
class PatientCreateTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/patient';
public function testCreatePatientSuccess()
{
$payload = [...];
$result = $this->withBodyFormat('json')
->post($this->endpoint, $payload);
$result->assertStatus(201);
}
}
Test Naming
- Use descriptive method names:
test<Action><Scenario><ExpectedResult> - Example:
testCreatePatientValidationFail,testCreatePatientSuccess
Test Status Codes
- 200: Success (GET, PATCH)
- 201: Created (POST)
- 400: Validation Error
- 401: Unauthorized
- 404: Not Found
- 500: Server Error
API Design
- Base URL:
/api/ - Authentication: JWT token via HttpOnly cookie
- Content-Type:
application/json - HTTP Methods:
GET- ReadPOST- CreatePATCH- Update (partial)DELETE- Delete
Routes Pattern
$routes->group('api/patient', function ($routes) {
$routes->get('/', 'Patient\PatientController::index');
$routes->post('/', 'Patient\PatientController::create');
$routes->get('(:num)', 'Patient\PatientController::show/$1');
$routes->patch('/', 'Patient\PatientController::update');
$routes->delete('/', 'Patient\PatientController::delete');
});
Security Guidelines
- Always use the
authfilter for protected routes - Sanitize all user inputs
- Use parameterized queries to prevent SQL injection
- Store JWT secret in
.envfile - Never commit
.envfiles
Project-Specific Conventions
Legacy Field Naming
Database uses PascalCase for column names (legacy convention):
PatientID,NameFirst,NameLastBirthdate,CreatedAt,UpdatedAt
ValueSet System
Use the App\Libraries\Lookups class for static dropdown values:
use App\Libraries\Lookups;
$genders = Lookups::get('gender');
$options = Lookups::getOptions('gender');
Models
Extend BaseModel for automatic UTC date handling:
<?php
namespace App\Models;
class PatientModel extends BaseModel
{
protected $table = 'patients';
protected $primaryKey = 'PatientID';
protected $allowedFields = ['NameFirst', 'NameLast', ...];
}
Environment Configuration
Database (.env)
database.default.hostname = localhost
database.default.database = clqms01
database.default.username = root
database.default.password = adminsakti
database.default.DBDriver = MySQLi
JWT Secret (.env)
JWT_SECRET = '5pandaNdutNdut'
Additional Notes
- API-Only: No view layer - this is a headless REST API
- Frontend Agnostic: Any client can consume these APIs
- Stateless: JWT-based authentication per request
- UTC Dates: All dates stored in UTC, converted for display
© 2025 5Panda Team. Engineering Precision in Clinical Diagnostics.