Init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
todos.json
|
10
.htaccess
Normal file
10
.htaccess
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Handle CORS preflight requests
|
||||||
|
RewriteCond %{REQUEST_METHOD} OPTIONS
|
||||||
|
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||||
|
|
||||||
|
# Route all requests to index.php
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^(.*)$ index.php [QSA,L]
|
29
Makefile
Normal file
29
Makefile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.PHONY: start clean install
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
.DEFAULT_GOAL := start
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
PHP = php
|
||||||
|
HOST = localhost
|
||||||
|
PORT = 8000
|
||||||
|
SERVER_URL = http://$(HOST):$(PORT)
|
||||||
|
|
||||||
|
# Start the PHP development server
|
||||||
|
start:
|
||||||
|
@echo "Starting PHP TODO API server at $(SERVER_URL)"
|
||||||
|
@echo "Available endpoints:"
|
||||||
|
@echo " GET $(SERVER_URL)/ - API info and available endpoints"
|
||||||
|
@echo " GET $(SERVER_URL)/todos - Get all todos"
|
||||||
|
@echo " POST $(SERVER_URL)/todos - Create todo"
|
||||||
|
@echo " PUT $(SERVER_URL)/todos/:id - Update todo"
|
||||||
|
@echo " DELETE $(SERVER_URL)/todos/:id - Delete todo"
|
||||||
|
@echo ""
|
||||||
|
@echo "Press Ctrl+C to stop the server"
|
||||||
|
$(PHP) -S $(HOST):$(PORT)
|
||||||
|
|
||||||
|
# Clean up data file
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning up data..."
|
||||||
|
@rm -f todos.json
|
||||||
|
@echo "Data file removed"
|
75
Todo.php
Normal file
75
Todo.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
class Todo {
|
||||||
|
private $id;
|
||||||
|
private $title;
|
||||||
|
private $description;
|
||||||
|
private $completed;
|
||||||
|
private $createdAt;
|
||||||
|
private $updatedAt;
|
||||||
|
|
||||||
|
public function __construct($id, $title, $description = '', $completed = false) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->description = $description;
|
||||||
|
$this->completed = $completed;
|
||||||
|
$this->createdAt = date('Y-m-d H:i:s');
|
||||||
|
$this->updatedAt = date('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
public function getId() {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription() {
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCompleted() {
|
||||||
|
return $this->completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt() {
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt() {
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
public function setTitle($title) {
|
||||||
|
$this->title = $title;
|
||||||
|
$this->updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDescription($description) {
|
||||||
|
$this->description = $description;
|
||||||
|
$this->updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCompleted($completed) {
|
||||||
|
$this->completed = $completed;
|
||||||
|
$this->updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateTimestamp() {
|
||||||
|
$this->updatedAt = date('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'description' => $this->description,
|
||||||
|
'completed' => $this->completed,
|
||||||
|
'created_at' => $this->createdAt,
|
||||||
|
'updated_at' => $this->updatedAt
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
188
TodoController.php
Normal file
188
TodoController.php
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'Todo.php';
|
||||||
|
|
||||||
|
class TodoController {
|
||||||
|
private $dataFile = 'todos.json';
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
// Create data file if it doesn't exist
|
||||||
|
if (!file_exists($this->dataFile)) {
|
||||||
|
file_put_contents($this->dataFile, json_encode([]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadTodos() {
|
||||||
|
$data = file_get_contents($this->dataFile);
|
||||||
|
$todosData = json_decode($data, true) ?: [];
|
||||||
|
|
||||||
|
$todos = [];
|
||||||
|
foreach ($todosData as $todoData) {
|
||||||
|
$todos[] = new Todo(
|
||||||
|
$todoData['id'],
|
||||||
|
$todoData['title'],
|
||||||
|
$todoData['description'] ?? '',
|
||||||
|
$todoData['completed'] ?? false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $todos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveTodos($todos) {
|
||||||
|
$todosData = [];
|
||||||
|
foreach ($todos as $todo) {
|
||||||
|
$todosData[] = $todo->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($this->dataFile, json_encode($todosData, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNextId() {
|
||||||
|
$todos = $this->loadTodos();
|
||||||
|
if (empty($todos)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxId = 0;
|
||||||
|
foreach ($todos as $todo) {
|
||||||
|
if ($todo->getId() > $maxId) {
|
||||||
|
$maxId = $todo->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $maxId + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllTodos() {
|
||||||
|
try {
|
||||||
|
$todos = $this->loadTodos();
|
||||||
|
$todosArray = [];
|
||||||
|
|
||||||
|
foreach ($todos as $todo) {
|
||||||
|
$todosArray[] = $todo->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($todosArray);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Failed to fetch todos: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createTodo() {
|
||||||
|
try {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!$input || !isset($input['title']) || empty(trim($input['title']))) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Title is required']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $this->getNextId();
|
||||||
|
$title = trim($input['title']);
|
||||||
|
$description = isset($input['description']) ? trim($input['description']) : '';
|
||||||
|
$completed = isset($input['completed']) ? (bool)$input['completed'] : false;
|
||||||
|
|
||||||
|
$todo = new Todo($id, $title, $description, $completed);
|
||||||
|
|
||||||
|
$todos = $this->loadTodos();
|
||||||
|
$todos[] = $todo;
|
||||||
|
$this->saveTodos($todos);
|
||||||
|
|
||||||
|
http_response_code(201);
|
||||||
|
echo json_encode($todo->toArray());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Failed to create todo: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateTodo($id) {
|
||||||
|
try {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!$input) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Invalid JSON input']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$todos = $this->loadTodos();
|
||||||
|
$todoIndex = -1;
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($todos); $i++) {
|
||||||
|
if ($todos[$i]->getId() === $id) {
|
||||||
|
$todoIndex = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($todoIndex === -1) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['error' => 'Todo not found']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$todo = $todos[$todoIndex];
|
||||||
|
|
||||||
|
// Update fields if provided
|
||||||
|
if (isset($input['title'])) {
|
||||||
|
if (empty(trim($input['title']))) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Title cannot be empty']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$todo->setTitle(trim($input['title']));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($input['description'])) {
|
||||||
|
$todo->setDescription(trim($input['description']));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($input['completed'])) {
|
||||||
|
$todo->setCompleted((bool)$input['completed']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveTodos($todos);
|
||||||
|
|
||||||
|
echo json_encode($todo->toArray());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Failed to update todo: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteTodo($id) {
|
||||||
|
try {
|
||||||
|
$todos = $this->loadTodos();
|
||||||
|
$todoIndex = -1;
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($todos); $i++) {
|
||||||
|
if ($todos[$i]->getId() === $id) {
|
||||||
|
$todoIndex = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($todoIndex === -1) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['error' => 'Todo not found']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deletedTodo = $todos[$todoIndex];
|
||||||
|
array_splice($todos, $todoIndex, 1);
|
||||||
|
$this->saveTodos($todos);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'message' => 'Todo deleted successfully',
|
||||||
|
'deleted_todo' => $deletedTodo->toArray()
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Failed to delete todo: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
91
index.php
Normal file
91
index.php
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||||
|
|
||||||
|
// Handle preflight OPTIONS requests
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'TodoController.php';
|
||||||
|
|
||||||
|
$controller = new TodoController();
|
||||||
|
|
||||||
|
// Get the request URI and method
|
||||||
|
$request_uri = $_SERVER['REQUEST_URI'];
|
||||||
|
$request_method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
|
||||||
|
// Remove query string if present
|
||||||
|
$path = strtok($request_uri, '?');
|
||||||
|
|
||||||
|
// Remove trailing slash if present
|
||||||
|
$path = rtrim($path, '/');
|
||||||
|
|
||||||
|
// If path is empty, set to root
|
||||||
|
if (empty($path)) {
|
||||||
|
$path = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route handling
|
||||||
|
switch ($path) {
|
||||||
|
case '/':
|
||||||
|
// Base route - show available endpoints
|
||||||
|
if ($request_method === 'GET') {
|
||||||
|
$host = $_SERVER['HTTP_HOST'];
|
||||||
|
$port = $_SERVER['SERVER_PORT'];
|
||||||
|
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'message' => "Todo API running at {$protocol}://{$host}:{$port}/",
|
||||||
|
'available_endpoints' => [
|
||||||
|
'GET /todos - Get all todos',
|
||||||
|
'POST /todos - Create todo',
|
||||||
|
'PUT /todos/:id - Update todo',
|
||||||
|
'DELETE /todos/:id - Delete todo'
|
||||||
|
]
|
||||||
|
], JSON_PRETTY_PRINT);
|
||||||
|
} else {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Method not allowed']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/todos':
|
||||||
|
switch ($request_method) {
|
||||||
|
case 'GET':
|
||||||
|
$controller->getAllTodos();
|
||||||
|
break;
|
||||||
|
case 'POST':
|
||||||
|
$controller->createTodo();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Method not allowed']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Handle /todos/:id routes
|
||||||
|
if (preg_match('/^\/todos\/(\d+)$/', $path, $matches)) {
|
||||||
|
$id = (int)$matches[1];
|
||||||
|
|
||||||
|
switch ($request_method) {
|
||||||
|
case 'PUT':
|
||||||
|
$controller->updateTodo($id);
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
$controller->deleteTodo($id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Method not allowed']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['error' => 'Endpoint not found']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
Reference in New Issue
Block a user