mirror of
https://github.com/JGH0/Todo-App-Backend.git
synced 2026-06-03 13:28:47 +02:00
added API and login
This commit is contained in:
45
app/Controllers/Api/V1/ActivityLogController.php
Normal file
45
app/Controllers/Api/V1/ActivityLogController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\ActivityLogModel;
|
||||
|
||||
class ActivityLogController extends BaseController
|
||||
{
|
||||
protected $activityLogModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->activityLogModel = new ActivityLogModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get activity logs for the authenticated user
|
||||
* GET /api/v1/activity-logs
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$limit = $this->request->getVar('limit') ?? 50;
|
||||
$logs = $this->activityLogModel->getByUser($userId, $limit);
|
||||
|
||||
return $this->successResponse($logs, 'Activity logs retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific activity log
|
||||
* GET /api/v1/activity-logs/{id}
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$log = $this->activityLogModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$log) {
|
||||
return $this->errorResponse('Activity log not found', 404);
|
||||
}
|
||||
|
||||
return $this->successResponse($log, 'Activity log retrieved successfully');
|
||||
}
|
||||
}
|
||||
238
app/Controllers/Api/V1/AuthController.php
Normal file
238
app/Controllers/Api/V1/AuthController.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\UserModel;
|
||||
use App\Models\ApiAuthKeyModel;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
protected $userModel;
|
||||
protected $apiAuthKeyModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userModel = new UserModel();
|
||||
$this->apiAuthKeyModel = new ApiAuthKeyModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle CORS preflight requests
|
||||
* OPTIONS /api/v1/auth/*
|
||||
*/
|
||||
public function options()
|
||||
{
|
||||
return $this->response
|
||||
->setStatusCode(200)
|
||||
->setHeader('Access-Control-Allow-Origin', '*')
|
||||
->setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
||||
->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user
|
||||
* POST /api/v1/auth/register
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'email' => [
|
||||
'rules' => 'required|valid_email|is_unique[users.email]',
|
||||
'errors' => [
|
||||
'required' => 'Email is required',
|
||||
'valid_email' => 'Please provide a valid email address',
|
||||
'is_unique' => 'This email is already registered'
|
||||
]
|
||||
],
|
||||
'password' => [
|
||||
'rules' => 'required|min_length[8]',
|
||||
'errors' => [
|
||||
'required' => 'Password is required',
|
||||
'min_length' => 'Password must be at least 8 characters long'
|
||||
]
|
||||
],
|
||||
'name' => [
|
||||
'rules' => 'required|max_length[255]',
|
||||
'errors' => [
|
||||
'required' => 'Name is required',
|
||||
'max_length' => 'Name must not exceed 255 characters'
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate UUID for user
|
||||
$userId = $this->generateUuid();
|
||||
|
||||
// Create user
|
||||
$userData = [
|
||||
'id' => $userId,
|
||||
'email' => $json['email'],
|
||||
'password_hash' => password_hash($json['password'], PASSWORD_BCRYPT),
|
||||
'name' => $json['name'],
|
||||
'avatar_url' => $json['avatar_url'] ?? null,
|
||||
'settings' => isset($json['settings']) && $json['settings'] ? json_encode($json['settings']) : json_encode(['theme' => 'light']),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
$this->userModel->insert($userData);
|
||||
|
||||
// Create API key for the new user
|
||||
$apiKey = $this->apiAuthKeyModel->createKey(
|
||||
$userId,
|
||||
'Default API Key',
|
||||
['read', 'write'],
|
||||
null
|
||||
);
|
||||
|
||||
// Remove sensitive data from response
|
||||
unset($userData['password_hash']);
|
||||
|
||||
return $this->successResponse([
|
||||
'user' => $userData,
|
||||
'api_key' => $apiKey['key'],
|
||||
'key_prefix' => $apiKey['prefix'],
|
||||
], 'User registered successfully', 201);
|
||||
} catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) {
|
||||
return $this->errorResponse('Database error: ' . $e->getMessage(), 500);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse('An error occurred: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login user and return API key
|
||||
* POST /api/v1/auth/login
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'email' => 'required|valid_email',
|
||||
'password' => 'required',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Authenticate user
|
||||
$user = $this->userModel->where('email', $json['email'])->first();
|
||||
|
||||
if (!$user || !password_verify($json['password'], $user['password_hash'])) {
|
||||
return $this->errorResponse('Invalid email or password', 401);
|
||||
}
|
||||
|
||||
// Check if user has an existing active API key
|
||||
$existingKey = $this->apiAuthKeyModel
|
||||
->where('user_id', $user['id'])
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if ($existingKey) {
|
||||
// Return existing key
|
||||
return $this->successResponse([
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'email' => $user['email'],
|
||||
'name' => $user['name'],
|
||||
],
|
||||
'api_key_prefix' => $existingKey['key_prefix'],
|
||||
'message' => 'Using existing API key',
|
||||
], 'Login successful');
|
||||
}
|
||||
|
||||
// Create new API key
|
||||
$apiKey = $this->apiAuthKeyModel->createKey(
|
||||
$user['id'],
|
||||
'Login API Key',
|
||||
['read', 'write'],
|
||||
null
|
||||
);
|
||||
|
||||
return $this->successResponse([
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'email' => $user['email'],
|
||||
'name' => $user['name'],
|
||||
],
|
||||
'api_key' => $apiKey['key'],
|
||||
'key_prefix' => $apiKey['prefix'],
|
||||
], 'Login successful');
|
||||
} catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) {
|
||||
return $this->errorResponse('Database error: ' . $e->getMessage(), 500);
|
||||
} catch (\Exception $e) {
|
||||
return $this->errorResponse('An error occurred: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an API key using email and password (legacy endpoint)
|
||||
* POST /api/v1/auth/api-key
|
||||
*/
|
||||
public function createApiKey()
|
||||
{
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'email' => 'required|valid_email',
|
||||
'password' => 'required|min_length[6]',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Authenticate user
|
||||
$user = $this->userModel->where('email', $json['email'])->first();
|
||||
|
||||
if (!$user || !password_verify($json['password'], $user['password_hash'])) {
|
||||
return $this->errorResponse('Invalid email or password', 401);
|
||||
}
|
||||
|
||||
// Create API key
|
||||
$name = $json['name'] ?? 'API Key';
|
||||
$scopes = $json['scopes'] ?? ['read', 'write'];
|
||||
$expiresAt = $json['expires_at'] ?? null;
|
||||
|
||||
$apiKey = $this->apiAuthKeyModel->createKey(
|
||||
$user['id'],
|
||||
$name,
|
||||
$scopes,
|
||||
$expiresAt
|
||||
);
|
||||
|
||||
return $this->successResponse([
|
||||
'key' => $apiKey['key'],
|
||||
'prefix' => $apiKey['prefix'],
|
||||
'name' => $apiKey['name'],
|
||||
'scopes' => $apiKey['scopes'],
|
||||
'expires_at' => $apiKey['expires_at'],
|
||||
], 'API key created successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate UUID
|
||||
*/
|
||||
private function generateUuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
133
app/Controllers/Api/V1/CategoryController.php
Normal file
133
app/Controllers/Api/V1/CategoryController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\CategoryModel;
|
||||
|
||||
class CategoryController extends BaseController
|
||||
{
|
||||
protected $categoryModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->categoryModel = new CategoryModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all categories for the authenticated user
|
||||
* GET /api/v1/categories
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$categories = $this->categoryModel->where('user_id', $userId)->findAll();
|
||||
|
||||
return $this->successResponse($categories, 'Categories retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new category
|
||||
* POST /api/v1/categories
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'name' => 'required|max_length[255]',
|
||||
'color' => 'required|max_length[7]',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $this->generateUuid(),
|
||||
'user_id' => $userId,
|
||||
'name' => $json['name'],
|
||||
'color' => $json['color'],
|
||||
'favorite' => $json['favorite'] ?? false,
|
||||
];
|
||||
|
||||
$this->categoryModel->insert($data);
|
||||
$category = $this->categoryModel->find($data['id']);
|
||||
|
||||
return $this->successResponse($category, 'Category created successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific category
|
||||
* GET /api/v1/categories/{id}
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$category = $this->categoryModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$category) {
|
||||
return $this->errorResponse('Category not found', 404);
|
||||
}
|
||||
|
||||
return $this->successResponse($category, 'Category retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a category
|
||||
* PUT /api/v1/categories/{id}
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$category = $this->categoryModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$category) {
|
||||
return $this->errorResponse('Category not found', 404);
|
||||
}
|
||||
|
||||
$json = $this->request->getJSON(true);
|
||||
$allowedFields = ['name', 'color', 'favorite'];
|
||||
$updateData = array_intersect_key($json, array_flip($allowedFields));
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->errorResponse('No valid fields to update');
|
||||
}
|
||||
|
||||
$this->categoryModel->update($id, $updateData);
|
||||
$category = $this->categoryModel->find($id);
|
||||
|
||||
return $this->successResponse($category, 'Category updated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a category
|
||||
* DELETE /api/v1/categories/{id}
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$category = $this->categoryModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$category) {
|
||||
return $this->errorResponse('Category not found', 404);
|
||||
}
|
||||
|
||||
$this->categoryModel->delete($id);
|
||||
|
||||
return $this->successResponse(null, 'Category deleted successfully');
|
||||
}
|
||||
|
||||
private function generateUuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
42
app/Controllers/Api/V1/MarketplaceController.php
Normal file
42
app/Controllers/Api/V1/MarketplaceController.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\MarketplaceThemeModel;
|
||||
|
||||
class MarketplaceController extends BaseController
|
||||
{
|
||||
protected $marketplaceThemeModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->marketplaceThemeModel = new MarketplaceThemeModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all marketplace themes
|
||||
* GET /api/v1/marketplace/themes
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$themes = $this->marketplaceThemeModel->getPublished();
|
||||
|
||||
return $this->successResponse($themes, 'Marketplace themes retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific marketplace theme
|
||||
* GET /api/v1/marketplace/themes/{id}
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$theme = $this->marketplaceThemeModel->find($id);
|
||||
|
||||
if (!$theme) {
|
||||
return $this->errorResponse('Theme not found', 404);
|
||||
}
|
||||
|
||||
return $this->successResponse($theme, 'Theme retrieved successfully');
|
||||
}
|
||||
}
|
||||
133
app/Controllers/Api/V1/ProjectController.php
Normal file
133
app/Controllers/Api/V1/ProjectController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\ProjectModel;
|
||||
|
||||
class ProjectController extends BaseController
|
||||
{
|
||||
protected $projectModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->projectModel = new ProjectModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all projects for the authenticated user
|
||||
* GET /api/v1/projects
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$projects = $this->projectModel->where('user_id', $userId)->findAll();
|
||||
|
||||
return $this->successResponse($projects, 'Projects retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new project
|
||||
* POST /api/v1/projects
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'name' => 'required|max_length[255]',
|
||||
'color' => 'required|max_length[7]',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $this->generateUuid(),
|
||||
'user_id' => $userId,
|
||||
'name' => $json['name'],
|
||||
'description' => $json['description'] ?? null,
|
||||
'color' => $json['color'],
|
||||
];
|
||||
|
||||
$this->projectModel->insert($data);
|
||||
$project = $this->projectModel->find($data['id']);
|
||||
|
||||
return $this->successResponse($project, 'Project created successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific project
|
||||
* GET /api/v1/projects/{id}
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$project = $this->projectModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$project) {
|
||||
return $this->errorResponse('Project not found', 404);
|
||||
}
|
||||
|
||||
return $this->successResponse($project, 'Project retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a project
|
||||
* PUT /api/v1/projects/{id}
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$project = $this->projectModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$project) {
|
||||
return $this->errorResponse('Project not found', 404);
|
||||
}
|
||||
|
||||
$json = $this->request->getJSON(true);
|
||||
$allowedFields = ['name', 'description', 'color'];
|
||||
$updateData = array_intersect_key($json, array_flip($allowedFields));
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->errorResponse('No valid fields to update');
|
||||
}
|
||||
|
||||
$this->projectModel->update($id, $updateData);
|
||||
$project = $this->projectModel->find($id);
|
||||
|
||||
return $this->successResponse($project, 'Project updated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a project
|
||||
* DELETE /api/v1/projects/{id}
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$project = $this->projectModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$project) {
|
||||
return $this->errorResponse('Project not found', 404);
|
||||
}
|
||||
|
||||
$this->projectModel->delete($id);
|
||||
|
||||
return $this->successResponse(null, 'Project deleted successfully');
|
||||
}
|
||||
|
||||
private function generateUuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
202
app/Controllers/Api/V1/RecurringTaskController.php
Normal file
202
app/Controllers/Api/V1/RecurringTaskController.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\RecurringTaskModel;
|
||||
use App\Models\RecurringTaskCategoryModel;
|
||||
|
||||
class RecurringTaskController extends BaseController
|
||||
{
|
||||
protected $recurringTaskModel;
|
||||
protected $recurringTaskCategoryModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->recurringTaskModel = new RecurringTaskModel();
|
||||
$this->recurringTaskCategoryModel = new RecurringTaskCategoryModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all recurring tasks for the authenticated user
|
||||
* GET /api/v1/recurring-tasks
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$tasks = $this->recurringTaskModel->getByUserWithCategories($userId);
|
||||
|
||||
return $this->successResponse($tasks, 'Recurring tasks retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new recurring task
|
||||
* POST /api/v1/recurring-tasks
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|max_length[255]',
|
||||
'schedule' => 'required|in_list[daily,weekly,monthly,custom]',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $this->generateUuid(),
|
||||
'user_id' => $userId,
|
||||
'title' => $json['title'],
|
||||
'description' => $json['description'] ?? null,
|
||||
'schedule' => $json['schedule'],
|
||||
'custom_days' => $json['custom_days'] ? json_encode($json['custom_days']) : json_encode([]),
|
||||
'favorite' => $json['favorite'] ?? false,
|
||||
];
|
||||
|
||||
$this->recurringTaskModel->insert($data);
|
||||
$task = $this->recurringTaskModel->getByUserWithCategories($userId, $data['id']);
|
||||
|
||||
return $this->successResponse($task, 'Recurring task created successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific recurring task
|
||||
* GET /api/v1/recurring-tasks/{id}
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$task = $this->recurringTaskModel->getByUserWithCategories($userId, $id);
|
||||
|
||||
if (!$task) {
|
||||
return $this->errorResponse('Recurring task not found', 404);
|
||||
}
|
||||
|
||||
return $this->successResponse($task, 'Recurring task retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a recurring task
|
||||
* PUT /api/v1/recurring-tasks/{id}
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$task = $this->recurringTaskModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$task) {
|
||||
return $this->errorResponse('Recurring task not found', 404);
|
||||
}
|
||||
|
||||
$json = $this->request->getJSON(true);
|
||||
$allowedFields = ['title', 'description', 'schedule', 'custom_days', 'favorite'];
|
||||
$updateData = array_intersect_key($json, array_flip($allowedFields));
|
||||
|
||||
if (isset($updateData['custom_days'])) {
|
||||
$updateData['custom_days'] = json_encode($updateData['custom_days']);
|
||||
}
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->errorResponse('No valid fields to update');
|
||||
}
|
||||
|
||||
$this->recurringTaskModel->update($id, $updateData);
|
||||
$task = $this->recurringTaskModel->getByUserWithCategories($userId, $id);
|
||||
|
||||
return $this->successResponse($task, 'Recurring task updated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a recurring task
|
||||
* DELETE /api/v1/recurring-tasks/{id}
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$task = $this->recurringTaskModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$task) {
|
||||
return $this->errorResponse('Recurring task not found', 404);
|
||||
}
|
||||
|
||||
$this->recurringTaskModel->delete($id);
|
||||
|
||||
return $this->successResponse(null, 'Recurring task deleted successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a category to a recurring task
|
||||
* POST /api/v1/recurring-tasks/{id}/categories
|
||||
*/
|
||||
public function addCategory($taskId = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = ['category_id' => 'required'];
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify task belongs to user
|
||||
$task = $this->recurringTaskModel->where('id', $taskId)->where('user_id', $userId)->first();
|
||||
if (!$task) {
|
||||
return $this->errorResponse('Recurring task not found', 404);
|
||||
}
|
||||
|
||||
// Check if link already exists
|
||||
$existing = $this->recurringTaskCategoryModel
|
||||
->where('recurring_task_id', $taskId)
|
||||
->where('category_id', $json['category_id'])
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return $this->errorResponse('Category already linked to this task', 409);
|
||||
}
|
||||
|
||||
$this->recurringTaskCategoryModel->insert([
|
||||
'recurring_task_id' => $taskId,
|
||||
'category_id' => $json['category_id'],
|
||||
]);
|
||||
|
||||
return $this->successResponse(null, 'Category added to recurring task successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a category from a recurring task
|
||||
* DELETE /api/v1/recurring-tasks/{id}/categories/{categoryId}
|
||||
*/
|
||||
public function removeCategory($taskId = null, $categoryId = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
|
||||
// Verify task belongs to user
|
||||
$task = $this->recurringTaskModel->where('id', $taskId)->where('user_id', $userId)->first();
|
||||
if (!$task) {
|
||||
return $this->errorResponse('Recurring task not found', 404);
|
||||
}
|
||||
|
||||
$this->recurringTaskCategoryModel
|
||||
->where('recurring_task_id', $taskId)
|
||||
->where('category_id', $categoryId)
|
||||
->delete();
|
||||
|
||||
return $this->successResponse(null, 'Category removed from recurring task successfully');
|
||||
}
|
||||
|
||||
private function generateUuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
202
app/Controllers/Api/V1/TodoController.php
Normal file
202
app/Controllers/Api/V1/TodoController.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\TodoModel;
|
||||
use App\Models\TodoCategoryModel;
|
||||
|
||||
class TodoController extends BaseController
|
||||
{
|
||||
protected $todoModel;
|
||||
protected $todoCategoryModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->todoModel = new TodoModel();
|
||||
$this->todoCategoryModel = new TodoCategoryModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all todos for the authenticated user
|
||||
* GET /api/v1/todos
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$todos = $this->todoModel->getByUserWithCategories($userId);
|
||||
|
||||
return $this->successResponse($todos, 'Todos retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new todo
|
||||
* POST /api/v1/todos
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|max_length[255]',
|
||||
'status' => 'permit_empty|in_list[open,in_progress,completed,archived]',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $this->generateUuid(),
|
||||
'user_id' => $userId,
|
||||
'title' => $json['title'],
|
||||
'description' => $json['description'] ?? null,
|
||||
'status' => $json['status'] ?? 'open',
|
||||
'due_date' => $json['due_date'] ?? null,
|
||||
'due_time' => $json['due_time'] ?? null,
|
||||
'sync_enabled' => $json['sync_enabled'] ?? true,
|
||||
'reminder_enabled' => $json['reminder_enabled'] ?? false,
|
||||
'recurring_enabled' => $json['recurring_enabled'] ?? false,
|
||||
'project_id' => $json['project_id'] ?? null,
|
||||
];
|
||||
|
||||
$this->todoModel->insert($data);
|
||||
$todo = $this->todoModel->getByUserWithCategories($userId, $data['id']);
|
||||
|
||||
return $this->successResponse($todo, 'Todo created successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific todo
|
||||
* GET /api/v1/todos/{id}
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$todo = $this->todoModel->getByUserWithCategories($userId, $id);
|
||||
|
||||
if (!$todo) {
|
||||
return $this->errorResponse('Todo not found', 404);
|
||||
}
|
||||
|
||||
return $this->successResponse($todo, 'Todo retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a todo
|
||||
* PUT /api/v1/todos/{id}
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$todo = $this->todoModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$todo) {
|
||||
return $this->errorResponse('Todo not found', 404);
|
||||
}
|
||||
|
||||
$json = $this->request->getJSON(true);
|
||||
$allowedFields = ['title', 'description', 'status', 'due_date', 'due_time', 'sync_enabled', 'reminder_enabled', 'recurring_enabled', 'project_id'];
|
||||
$updateData = array_intersect_key($json, array_flip($allowedFields));
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->errorResponse('No valid fields to update');
|
||||
}
|
||||
|
||||
$this->todoModel->update($id, $updateData);
|
||||
$todo = $this->todoModel->getByUserWithCategories($userId, $id);
|
||||
|
||||
return $this->successResponse($todo, 'Todo updated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a todo
|
||||
* DELETE /api/v1/todos/{id}
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$todo = $this->todoModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$todo) {
|
||||
return $this->errorResponse('Todo not found', 404);
|
||||
}
|
||||
|
||||
$this->todoModel->delete($id);
|
||||
|
||||
return $this->successResponse(null, 'Todo deleted successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a category to a todo
|
||||
* POST /api/v1/todos/{id}/categories
|
||||
*/
|
||||
public function addCategory($todoId = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = ['category_id' => 'required'];
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify todo belongs to user
|
||||
$todo = $this->todoModel->where('id', $todoId)->where('user_id', $userId)->first();
|
||||
if (!$todo) {
|
||||
return $this->errorResponse('Todo not found', 404);
|
||||
}
|
||||
|
||||
// Check if link already exists
|
||||
$existing = $this->todoCategoryModel
|
||||
->where('todo_id', $todoId)
|
||||
->where('category_id', $json['category_id'])
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return $this->errorResponse('Category already linked to this todo', 409);
|
||||
}
|
||||
|
||||
$this->todoCategoryModel->insert([
|
||||
'todo_id' => $todoId,
|
||||
'category_id' => $json['category_id'],
|
||||
]);
|
||||
|
||||
return $this->successResponse(null, 'Category added to todo successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a category from a todo
|
||||
* DELETE /api/v1/todos/{id}/categories/{categoryId}
|
||||
*/
|
||||
public function removeCategory($todoId = null, $categoryId = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
|
||||
// Verify todo belongs to user
|
||||
$todo = $this->todoModel->where('id', $todoId)->where('user_id', $userId)->first();
|
||||
if (!$todo) {
|
||||
return $this->errorResponse('Todo not found', 404);
|
||||
}
|
||||
|
||||
$this->todoCategoryModel
|
||||
->where('todo_id', $todoId)
|
||||
->where('category_id', $categoryId)
|
||||
->delete();
|
||||
|
||||
return $this->successResponse(null, 'Category removed from todo successfully');
|
||||
}
|
||||
|
||||
private function generateUuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
126
app/Controllers/Api/V1/UserController.php
Normal file
126
app/Controllers/Api/V1/UserController.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\UserModel;
|
||||
use App\Models\ApiAuthKeyModel;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
protected $userModel;
|
||||
protected $apiAuthKeyModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userModel = new UserModel();
|
||||
$this->apiAuthKeyModel = new ApiAuthKeyModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
* GET /api/v1/user/profile
|
||||
*/
|
||||
public function profile()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$user = $this->userModel->find($userId);
|
||||
|
||||
if (!$user) {
|
||||
return $this->errorResponse('User not found', 404);
|
||||
}
|
||||
|
||||
// Remove sensitive data
|
||||
unset($user['password_hash']);
|
||||
|
||||
return $this->successResponse($user, 'Profile retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
* PUT /api/v1/user/profile
|
||||
*/
|
||||
public function updateProfile()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$allowedFields = ['name', 'avatar_url', 'settings'];
|
||||
$updateData = array_intersect_key($json, array_flip($allowedFields));
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->errorResponse('No valid fields to update');
|
||||
}
|
||||
|
||||
$this->userModel->update($userId, $updateData);
|
||||
$user = $this->userModel->find($userId);
|
||||
unset($user['password_hash']);
|
||||
|
||||
return $this->successResponse($user, 'Profile updated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* List user's API keys
|
||||
* GET /api/v1/user/api-keys
|
||||
*/
|
||||
public function listApiKeys()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$apiKeys = $this->apiAuthKeyModel->getByUser($userId);
|
||||
|
||||
// Remove sensitive data
|
||||
foreach ($apiKeys as &$key) {
|
||||
unset($key['key_hash']);
|
||||
}
|
||||
|
||||
return $this->successResponse($apiKeys, 'API keys retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new API key
|
||||
* POST /api/v1/user/api-keys
|
||||
*/
|
||||
public function createApiKey()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$name = $json['name'] ?? 'API Key';
|
||||
$scopes = $json['scopes'] ?? ['read', 'write'];
|
||||
$expiresAt = $json['expires_at'] ?? null;
|
||||
|
||||
$apiKey = $this->apiAuthKeyModel->createKey(
|
||||
$userId,
|
||||
$name,
|
||||
$scopes,
|
||||
$expiresAt
|
||||
);
|
||||
|
||||
return $this->successResponse([
|
||||
'id' => $apiKey['id'],
|
||||
'key' => $apiKey['key'],
|
||||
'prefix' => $apiKey['prefix'],
|
||||
'name' => $apiKey['name'],
|
||||
'scopes' => $apiKey['scopes'],
|
||||
'expires_at' => $apiKey['expires_at'],
|
||||
], 'API key created successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an API key
|
||||
* DELETE /api/v1/user/api-keys/{id}
|
||||
*/
|
||||
public function revokeApiKey($id)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$apiKey = $this->apiAuthKeyModel->find($id);
|
||||
|
||||
if (!$apiKey || $apiKey['user_id'] !== $userId) {
|
||||
return $this->errorResponse('API key not found', 404);
|
||||
}
|
||||
|
||||
$this->apiAuthKeyModel->revokeKey($id);
|
||||
|
||||
return $this->successResponse(null, 'API key revoked successfully');
|
||||
}
|
||||
}
|
||||
120
app/Controllers/Api/V1/UserThemeController.php
Normal file
120
app/Controllers/Api/V1/UserThemeController.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api\V1;
|
||||
|
||||
use App\Controllers\Api\BaseController;
|
||||
use App\Models\UserThemeModel;
|
||||
|
||||
class UserThemeController extends BaseController
|
||||
{
|
||||
protected $userThemeModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userThemeModel = new UserThemeModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all themes for the authenticated user
|
||||
* GET /api/v1/user/themes
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$themes = $this->userThemeModel->getByUser($userId);
|
||||
|
||||
return $this->successResponse($themes, 'User themes retrieved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user theme
|
||||
* POST /api/v1/user/themes
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$json = $this->request->getJSON(true);
|
||||
|
||||
$rules = [
|
||||
'theme_id' => 'required',
|
||||
];
|
||||
|
||||
if (!$this->validateRequest($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $this->generateUuid(),
|
||||
'user_id' => $userId,
|
||||
'theme_id' => $json['theme_id'],
|
||||
'is_active' => $json['is_active'] ?? false,
|
||||
'custom_settings' => $json['custom_settings'] ? json_encode($json['custom_settings']) : null,
|
||||
];
|
||||
|
||||
$this->userThemeModel->insert($data);
|
||||
$theme = $this->userThemeModel->find($data['id']);
|
||||
|
||||
return $this->successResponse($theme, 'User theme created successfully', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user theme
|
||||
* PUT /api/v1/user/themes/{id}
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$theme = $this->userThemeModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$theme) {
|
||||
return $this->errorResponse('User theme not found', 404);
|
||||
}
|
||||
|
||||
$json = $this->request->getJSON(true);
|
||||
$allowedFields = ['is_active', 'custom_settings'];
|
||||
$updateData = array_intersect_key($json, array_flip($allowedFields));
|
||||
|
||||
if (isset($updateData['custom_settings'])) {
|
||||
$updateData['custom_settings'] = json_encode($updateData['custom_settings']);
|
||||
}
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->errorResponse('No valid fields to update');
|
||||
}
|
||||
|
||||
$this->userThemeModel->update($id, $updateData);
|
||||
$theme = $this->userThemeModel->find($id);
|
||||
|
||||
return $this->successResponse($theme, 'User theme updated successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user theme
|
||||
* DELETE /api/v1/user/themes/{id}
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
$userId = $this->getUserId();
|
||||
$theme = $this->userThemeModel->where('id', $id)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$theme) {
|
||||
return $this->errorResponse('User theme not found', 404);
|
||||
}
|
||||
|
||||
$this->userThemeModel->delete($id);
|
||||
|
||||
return $this->successResponse(null, 'User theme deleted successfully');
|
||||
}
|
||||
|
||||
private function generateUuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user