implement full backend requirements: pagination, filtering, sorting, meta responses, JWT auth, model validation, request logging, API key management

- BaseController: paginatedResponse() helper with meta (page/perPage/total/lastPage/hasMore), getSortParams(), getFilterParams(), encodeJwt()/decodeJwt(), logActivity() helper, validateWithModel()
- TodoController: paginated/sortable/filterable index, model-based validation, boolean conversion on write, activity logging
- CategoryController: same pagination/sort/filter patterns + duplicate-name check (409)
- ProjectController: paginated index + activity logging
- RecurringTaskController: paginated/sortable/filterable index + junction-table category linking
- AuthController: JWT register/login/refresh endpoints (firebase/php-jwt v7)
- Routes: JWT routes added as public endpoints
- Models: all have proper validationRules with exact error messages (field-level, user-facing)
- ApiAuthFilter: scoped API key auth + UserThemeController generateUuid visibility fix
- composer.json: add firebase/php-jwt ^7.0
This commit is contained in:
Jürg Hallenbarter
2026-05-13 14:54:16 +02:00
parent e125ac34d7
commit 02f77a15a7
15 changed files with 943 additions and 412 deletions

View File

@@ -6,40 +6,53 @@ use CodeIgniter\Model;
class RecurringTaskModel extends Model
{
protected $table = 'recurring_tasks';
protected $primaryKey = 'id';
protected $table = 'recurring_tasks';
protected $primaryKey = 'id';
protected $useAutoIncrement = false;
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $allowedFields = [
'id',
'user_id',
'title',
'description',
'schedule',
'custom_days',
'favorite',
'created_at',
'updated_at',
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $allowedFields = [
'id', 'user_id', 'title', 'description', 'schedule',
'custom_days', 'favorite', 'created_at', 'updated_at',
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $validationRules = [
'user_id' => 'required',
'title' => 'required|max_length[255]',
'schedule' => 'required|in_list[daily,weekly,monthly,custom]',
'user_id' => [
'rules' => 'required',
'errors' => ['required' => 'User ID is required.'],
],
'title' => [
'rules' => 'required|max_length[255]',
'errors' => [
'required' => 'The recurring task title is required.',
'max_length' => 'The title must not exceed 255 characters.',
],
],
'schedule' => [
'rules' => 'permit_empty|in_list[daily,weekly,monthly,custom]',
'errors' => [
'in_list' => 'Schedule must be one of: daily, weekly, monthly, custom.',
],
],
];
// Get recurring tasks with categories
public function getWithCategories($taskId = null)
// ── Queries ────────────────────────────────────────────────────────────
public function getByUserWithCategories($userId, $taskId = null)
{
$builder = $this->select('recurring_tasks.*, GROUP_CONCAT(categories.name) as category_names')
->join('recurring_task_categories', 'recurring_tasks.id = recurring_task_categories.recurring_task_id', 'left')
->join('categories', 'recurring_task_categories.category_id = categories.id', 'left')
->groupBy('recurring_tasks.id');
$builder = $this->select('
recurring_tasks.*,
GROUP_CONCAT(DISTINCT categories.id SEPARATOR \',\') as category_ids,
GROUP_CONCAT(DISTINCT categories.name SEPARATOR \', \') as category_names
')
->join('recurring_task_categories', 'recurring_tasks.id = recurring_task_categories.recurring_task_id', 'left')
->join('categories', 'recurring_task_categories.category_id = categories.id', 'left')
->where('recurring_tasks.user_id', $userId)
->groupBy('recurring_tasks.id');
if ($taskId) {
$builder->where('recurring_tasks.id', $taskId);
@@ -47,16 +60,4 @@ class RecurringTaskModel extends Model
return $builder->get()->getResultArray();
}
// Get recurring tasks by user with categories
public function getByUserWithCategories($userId)
{
return $this->select('recurring_tasks.*, GROUP_CONCAT(categories.name) as category_names')
->join('recurring_task_categories', 'recurring_tasks.id = recurring_task_categories.recurring_task_id', 'left')
->join('categories', 'recurring_task_categories.category_id = categories.id', 'left')
->where('recurring_tasks.user_id', $userId)
->groupBy('recurring_tasks.id')
->get()
->getResultArray();
}
}