Files
opensourcepos/app/Controllers/Api/BaseController.php
Ollama e45af91e2e feat: Add REST API implementation with API key authentication
Implement comprehensive REST API for OSPOS with the following:

Database:
- Migration for ospos_api_keys table
- Seeder for module permissions

Models:
- ApiKey model with key generation, validation, revocation
- SHA-256 hashing for secure key storage
- Support for key expiration

Filters:
- ApiAuth filter for X-API-Key header authentication
- CSRF exemption for API routes

Controllers:
- Api/BaseController with response helpers and field transformation
- Api/Customers (CRUD + batch delete, suggestions)
- Api/Suppliers (CRUD + batch delete, suggestions)
- Api/Items (CRUD + batch delete, quantities endpoint)
- Api/Inventory (adjustments with set/adjust modes, bulk support)
- ApiKeys (UI controller for key management)

Routes:
- /api/v1/* endpoints with apiauth filter
- /office/api-keys/* endpoints for key management UI

Tests:
- ApiKeyTest for model functionality
- ApiAuthTest for authentication filter

Features:
- camelCase JSON field names (API standard)
- Offset/limit pagination
- Soft delete support
- Permission-based authorization
- Key prefix for UI identification
- Last used timestamp tracking

Refs: #2463, #615, #3789, #3809, #1680, #876, #1959, #157
2026-03-06 14:35:27 +00:00

129 lines
3.9 KiB
PHP

<?php
namespace App\Controllers\Api;
use App\Models\Employee;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
class BaseController extends ResourceController
{
protected Employee $employee;
protected int $employeeId = 0;
protected $format = 'json';
public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger): void
{
parent::initController($request, $response, $logger);
$this->employee = model(Employee::class);
$this->employeeId = $request->employeeId ?? 0;
}
protected function hasPermission(string $moduleId): bool
{
return $this->employee->has_grant($moduleId, $this->employeeId);
}
protected function respondSuccess(array $data = [], int $code = 200, string $message = 'Success'): ResponseInterface
{
$response = ['success' => true];
if ($message) {
$response['message'] = $message;
}
$response = array_merge($response, $data);
return $this->respond($response, $code);
}
protected function respondCreated(array $data = [], string $message = 'Resource created'): ResponseInterface
{
return $this->respondSuccess($data, 201, $message);
}
protected function respondError(string $message, int $code = 400): ResponseInterface
{
return $this->respond([
'success' => false,
'message' => $message
], $code);
}
protected function respondNotFound(string $message = 'Resource not found'): ResponseInterface
{
return $this->respondError($message, 404);
}
protected function respondUnauthorized(string $message = 'Unauthorized'): ResponseInterface
{
return $this->respondError($message, 403);
}
protected function respondValidationError(array $errors): ResponseInterface
{
return $this->respond([
'success' => false,
'message' => 'Validation failed',
'errors' => $errors
], 422);
}
protected function getPagination(): array
{
$offset = (int) ($this->request->getGet('offset') ?? 0);
$limit = (int) ($this->request->getGet('limit') ?? 25);
$limit = min(max($limit, 1), 100);
$offset = max($offset, 0);
return ['offset' => $offset, 'limit' => $limit];
}
protected function getSort(array $allowedFields, string $default = 'id', string $defaultOrder = 'asc'): array
{
$sort = $this->request->getGet('sort') ?? $default;
$order = strtolower($this->request->getGet('order') ?? $defaultOrder);
if (!in_array($sort, $allowedFields)) {
$sort = $default;
}
if (!in_array($order, ['asc', 'desc'])) {
$order = $defaultOrder;
}
return ['sort' => $sort, 'order' => $order];
}
protected function toCamelCase(array $data): array
{
$result = [];
foreach ($data as $key => $value) {
$camelKey = lcfirst(str_replace('_', '', ucwords($key, '_')));
$result[$camelKey] = $value;
}
return $result;
}
protected function toSnakeCase(array $data): array
{
$result = [];
foreach ($data as $key => $value) {
$snakeKey = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $key));
$result[$snakeKey] = $value;
}
return $result;
}
protected function transformItem(object|array $item, array $additional = []): array
{
$item = is_object($item) ? (array) $item : $item;
return $this->toCamelCase(array_merge($item, $additional));
}
protected function transformCollection(array $items): array
{
return array_map([$this, 'transformItem'], $items);
}
}