clqms-be/AGENTS.md
mahdahar ece101b6d2 Add audit logging plan documentation and update test infrastructure
- 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)
2026-02-19 13:20:24 +07:00

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 to app/, Config\ maps to app/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 - Read
    • POST - Create
    • PATCH - 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 auth filter for protected routes
  • Sanitize all user inputs
  • Use parameterized queries to prevent SQL injection
  • Store JWT secret in .env file
  • Never commit .env files

Project-Specific Conventions

Legacy Field Naming

Database uses PascalCase for column names (legacy convention):

  • PatientID, NameFirst, NameLast
  • Birthdate, 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.