Skip to content

Instantly share code, notes, and snippets.

@hoangsonww
Last active December 20, 2024 15:03
Show Gist options
  • Select an option

  • Save hoangsonww/41eeb072773417e4feec3489a5fcaab2 to your computer and use it in GitHub Desktop.

Select an option

Save hoangsonww/41eeb072773417e4feec3489a5fcaab2 to your computer and use it in GitHub Desktop.
A single-file PHP backend for a Blog Management API with user authentication, role-based access control, and CRUD operations for posts and comments using SQLite.
<?php
// Enable CORS and set content type
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
// SQLite database connection
$db = new PDO('sqlite:blog.db');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Initialize tables
function initializeDatabase($db) {
$db->exec("CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('admin', 'author'))
)");
$db->exec("CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
author_id INTEGER NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id)
)");
$db->exec("CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER NOT NULL,
author_id INTEGER NOT NULL,
content TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (author_id) REFERENCES users(id)
)");
}
initializeDatabase($db);
// Authentication functions
function generateToken($username) {
return base64_encode($username . '.' . time());
}
function verifyToken($token) {
$decoded = base64_decode($token);
if ($decoded) {
$parts = explode('.', $decoded);
return (count($parts) === 2) ? $parts[0] : false;
}
return false;
}
// Response helper function
function sendResponse($status_code, $data) {
http_response_code($status_code);
echo json_encode($data);
exit;
}
// Route handling
$method = $_SERVER['REQUEST_METHOD'];
$path = explode('/', trim($_SERVER['PATH_INFO'], '/'));
$input = json_decode(file_get_contents('php://input'), true);
// Handle user registration
if ($method === 'POST' && $path[0] === 'register') {
$username = $input['username'] ?? null;
$password = $input['password'] ?? null;
$role = $input['role'] ?? 'author';
if (!$username || !$password || !in_array($role, ['admin', 'author'])) {
sendResponse(400, ['error' => 'Invalid input']);
}
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
try {
$stmt = $db->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmt->execute([$username, $hashedPassword, $role]);
sendResponse(201, ['message' => 'User registered successfully']);
} catch (PDOException $e) {
sendResponse(400, ['error' => 'Username already exists']);
}
// Handle user login
} elseif ($method === 'POST' && $path[0] === 'login') {
$username = $input['username'] ?? null;
$password = $input['password'] ?? null;
if (!$username || !$password) {
sendResponse(400, ['error' => 'Invalid input']);
}
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$token = generateToken($username);
sendResponse(200, ['token' => $token, 'role' => $user['role']]);
} else {
sendResponse(401, ['error' => 'Invalid credentials']);
}
// Handle creating a new post
} elseif ($method === 'POST' && $path[0] === 'posts') {
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$username = verifyToken($token);
if (!$username) {
sendResponse(401, ['error' => 'Unauthorized']);
}
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user['role'] !== 'author' && $user['role'] !== 'admin') {
sendResponse(403, ['error' => 'Forbidden']);
}
$title = $input['title'] ?? null;
$content = $input['content'] ?? null;
if (!$title || !$content) {
sendResponse(400, ['error' => 'Invalid input']);
}
$stmt = $db->prepare("INSERT INTO posts (title, content, author_id) VALUES (?, ?, ?)");
$stmt->execute([$title, $content, $user['id']]);
sendResponse(201, ['message' => 'Post created successfully']);
// Handle retrieving all posts
} elseif ($method === 'GET' && $path[0] === 'posts') {
$stmt = $db->query("SELECT posts.id, posts.title, posts.content, users.username as author, posts.created_at
FROM posts JOIN users ON posts.author_id = users.id");
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
sendResponse(200, $posts);
// Handle creating a comment
} elseif ($method === 'POST' && $path[0] === 'comments') {
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$username = verifyToken($token);
if (!$username) {
sendResponse(401, ['error' => 'Unauthorized']);
}
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$postId = $input['post_id'] ?? null;
$content = $input['content'] ?? null;
if (!$postId || !$content) {
sendResponse(400, ['error' => 'Invalid input']);
}
$stmt = $db->prepare("INSERT INTO comments (post_id, author_id, content) VALUES (?, ?, ?)");
$stmt->execute([$postId, $user['id'], $content]);
sendResponse(201, ['message' => 'Comment added successfully']);
// Handle retrieving comments for a specific post
} elseif ($method === 'GET' && $path[0] === 'comments' && isset($path[1])) {
$postId = $path[1];
$stmt = $db->prepare("SELECT comments.id, comments.content, users.username as author, comments.created_at
FROM comments JOIN users ON comments.author_id = users.id
WHERE comments.post_id = ?");
$stmt->execute([$postId]);
$comments = $stmt->fetchAll(PDO::FETCH_ASSOC);
sendResponse(200, $comments);
// Handle invalid endpoints
} else {
sendResponse(405, ['error' => 'Method not allowed or invalid endpoint']);
}
?>
@hoangsonww
Copy link
Author

Blog Management API

A simple yet comprehensive PHP backend for a Blog Management API that allows user registration, login, role-based access control, and CRUD operations for blog posts and comments. This backend uses an SQLite database and is implemented in a single PHP file.

Features

  • User Authentication: Register and login with secure password hashing and token-based authentication.
  • Role-Based Access Control: Admin and author roles with different levels of access.
  • CRUD Operations: Create, read, update, and delete operations for blog posts and comments.
  • Data Storage: Uses an SQLite database for persistent data storage.
  • Error Handling: Returns meaningful error messages and HTTP status codes for different scenarios.
  • Single File Implementation: All functionality is contained within a single PHP file for simplicity.

Setup Instructions

Requirements

  • PHP 7.4 or later
  • SQLite extension enabled in PHP

Installation

  1. Download or Clone the Gist

    Download the blog-api.php file or clone this Gist to your local machine.

  2. Run the PHP Server

    Navigate to the directory containing blog-api.php and run the PHP development server:

    php -S localhost:8000

    This command will start the server at http://localhost:8000.

API Endpoints

1. Register User

  • Endpoint: POST /register
  • Description: Registers a new user with a role of either admin or author.
  • Request Body:
    {
      "username": "user1",
      "password": "password123",
      "role": "author"
    }
  • Response:
    • 201: User registered successfully.
    • 400: Invalid input or username already exists.

2. Login User

  • Endpoint: POST /login
  • Description: Logs in a user and returns an authentication token.
  • Request Body:
    {
      "username": "user1",
      "password": "password123"
    }
  • Response:
    • 200: Returns a JSON object with the authentication token and user role.
    • 401: Invalid credentials.

3. Create a Blog Post

  • Endpoint: POST /posts
  • Description: Creates a new blog post. Requires Authorization header with a valid token.
  • Request Headers:
    • Authorization: Bearer <token>
  • Request Body:
    {
      "title": "My First Post",
      "content": "This is the content of the post."
    }
  • Response:
    • 201: Post created successfully.
    • 401: Unauthorized (invalid or missing token).
    • 403: Forbidden (insufficient role).

4. Retrieve All Blog Posts

  • Endpoint: GET /posts
  • Description: Retrieves all blog posts with their respective authors.
  • Response:
    • 200: Returns a list of all blog posts.

5. Create a Comment

  • Endpoint: POST /comments
  • Description: Creates a new comment on a specific post. Requires Authorization header with a valid token.
  • Request Headers:
    • Authorization: Bearer <token>
  • Request Body:
    {
      "post_id": 1,
      "content": "This is a comment on the post."
    }
  • Response:
    • 201: Comment added successfully.
    • 401: Unauthorized (invalid or missing token).

6. Retrieve Comments for a Specific Post

  • Endpoint: GET /comments/{post_id}
  • Description: Retrieves all comments for a specific post.
  • Response:
    • 200: Returns a list of comments for the specified post.

Usage Examples

1. Register a New User

Use a tool like Postman or curl to send a POST request to register a new user.

curl -X POST http://localhost:8000/blog-api.php/register \
-H "Content-Type: application/json" \
-d '{"username": "user1", "password": "password123", "role": "author"}'

2. Login as a User

Send a POST request to login as a user and obtain an authentication token.

curl -X POST http://localhost:8000/blog-api.php/login \
-H "Content-Type: application/json" \
-d '{"username": "user1", "password": "password123"}'

3. Create a New Post

Send a POST request to create a new blog post using the token obtained from the login.

curl -X POST http://localhost:8000/blog-api.php/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your_token>" \
-d '{"title": "My First Post", "content": "This is the content of the post."}'

Error Handling

The API returns appropriate HTTP status codes and JSON error messages for different error scenarios:

  • 400: Bad Request – Invalid input or missing fields.
  • 401: Unauthorized – Invalid or missing authentication token.
  • 403: Forbidden – Insufficient permissions.
  • 404: Not Found – Resource not found.
  • 405: Method Not Allowed – Invalid HTTP method or endpoint.

Security Considerations

  • Passwords are securely hashed using bcrypt.
  • Simple token-based authentication is implemented, but in a production environment, consider using more robust mechanisms like JWT.
  • Ensure the API is served over HTTPS in a production environment to secure sensitive data.

Contributing

Feel free to fork this Gist and contribute by submitting pull requests with improvements or additional features.

License

This project is open-source and available under the MIT License.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment