WP Manifestindependent plugin directory
manifest / integrations / crud-api-for-events

Author Events API

Production-ready WordPress REST API plugin for managing author events.

by Sarfaraz Kazi · github.com/sarfaraz-kazi/crud-api-for-events · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/sarfaraz-kazi/crud-api-for-events/archive/refs/heads/main.zip

A production-ready WordPress REST API plugin for managing author events. Built to enterprise standards: OOP/PHP 8.0+, a dedicated WP_REST_Controller subclass, JSON Schema validation, capability-gated permissions, and a full PHPUnit test suite.


Table of Contents


Features

  • Custom Post Type author_event — leverages the full WordPress data layer (meta API, revisions, trash/restore, capabilities)
  • Custom Taxonomy event_category — hierarchical, admin-only
  • REST API under author-events/v1/events extending WP_REST_Controller
  • Full CRUDGET (collection + single), POST, PUT/PATCH, DELETE
  • Date filtering via ?date=YYYY-MM-DD on the collection endpoint
  • JSON Schema validation (get_item_schema) — type coercion, required fields, and format checks are handled automatically per endpoint
  • Capability guards — every endpoint requires manage_options; unauthenticated requests get 401, authenticated non-admins get 403
  • HATEOAS linksself and collection links on every item response
  • Pagination headersX-WP-Total and X-WP-TotalPages on collection responses
  • PSR-4 autoloader — no Composer dependency at runtime; switchable to Composer in one line
  • 13-case PHPUnit test suite covering auth, CRUD, date filtering, DB verification, and response contract

Requirements

Dependency Minimum version
PHP 8.0
WordPress 6.0
PHPUnit (dev only) 9.6 or 10.x

Installation

Via upload

  1. Download or clone this repository.
  2. Copy the author-events-api/ folder into wp-content/plugins/.
  3. Activate the plugin in Plugins → Installed Plugins.

Via Composer (dev environment)

git clone https://github.com/sarfaraz-kazi/CRUD-API-For-Events
composer install

No Composer autoload is required at runtime — the plugin ships its own spl_autoload_register loader.


Architecture

author-events-api/
├── author-events-api.php               ← Entry point: constants + PSR-4 autoloader
├── composer.json
├── phpunit.xml
├── includes/
│   ├── class-plugin.php                ← Singleton bootstrap (wires all hooks)
│   ├── class-cpt-registration.php      ← CPT, taxonomy, and post meta registration
│   └── api/
│       └── class-events-controller.php ← WP_REST_Controller subclass
└── tests/
    ├── bootstrap.php
    └── class-events-controller-test.php

Design decisions

WP_REST_Controller subclass, not procedural callbacks get_endpoint_args_for_item_schema() reads the JSON Schema once and auto-generates the args array for every route — type coercion, sanitisation, and required validation included. filter_response_by_context() strips fields per context (view / edit / embed) without any per-endpoint logic.

CPT storage over a custom table The WordPress data layer provides meta API, WP_Query with meta_query / tax_query, revisions, trash/restore, and capability maps out of the box. A custom table is warranted only when query patterns outgrow what WP_Query can express efficiently — unnecessary complexity for an MVP.

Separate entry point and Plugin class author-events-api.php only defines constants and registers the autoloader. All hook wiring lives in Plugin::get_instance(), so every component can be instantiated in isolation during tests without bootstrapping the whole plugin.

CHAR type in meta_query BETWEEN Dates are stored as ISO 8601 (YYYY-MM-DDTHH:MM:SS). MySQL's DATETIME type expects a space separator; CHAR keeps the comparison lexicographic, which is correct because ISO 8601 dates sort identically to their chronological order when zero-padded.


API Reference

Authentication

All endpoints require the current user to have the manage_options capability (WordPress Administrators by default). Authenticate using WordPress Application Passwords or cookie-based auth with a valid nonce.

Authorization: Basic base64(username:application-password)
Condition HTTP status
Not logged in 401 Unauthorized
Logged in, insufficient capability 403 Forbidden

GET /events

Returns a paginated list of events, ordered by start_date ascending.

Query parameters

Parameter Type Description
date string (YYYY-MM-DD) Filter events whose start_date falls on this day
category string Filter by event_category slug
page integer Page number (default 1)
per_page integer Items per page (default 10, max 100)

Example request

curl -u admin:app-password \
  "https://example.com/wp-json/author-events/v1/events?date=2025-06-15&per_page=5"

Example response 200 OK

[
  {
    "id": 42,
    "title": "Book Launch: The Digital Age",
    "description": "<p>Join us for the official launch...</p>",
    "category": "book-launch",
    "start_date": "2025-06-15T09:00:00",
    "end_date": "2025-06-15T11:00:00",
    "status": "publish",
    "date_created": "2025-05-01T12:00:00",
    "date_modified": "2025-05-10T08:30:00",
    "_links": {
      "self":       [{ "href": "https://example.com/wp-json/author-events/v1/events/42" }],
      "collection": [{ "href": "https://example.com/wp-json/author-events/v1/events" }]
    }
  }
]

Response headers

X-WP-Total: 12
X-WP-TotalPages: 3

GET /events/{id}

Returns a single event by its WordPress post ID.

curl -u admin:app-password \
  "https://example.com/wp-json/author-events/v1/events/42"
Status Meaning
200 OK Event found
404 Not Found No event with that ID
410 Gone Event exists but is in the trash

POST /events

Creates a new event.

Request body (JSON)

Field Type Required Description
title string Yes Event title (1–200 characters)
description string No Full HTML description
category string No Category name — created automatically if it does not exist
start_date string Yes ISO 8601 datetime (YYYY-MM-DDTHH:MM:SS)
end_date string Yes ISO 8601 datetime — must be ≥ start_date
status string No publish (default), draft, pending, private

Example request

curl -u admin:app-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "title":       "Annual Author Summit",
    "description": "<p>Gathering of published authors.</p>",
    "category":    "summit",
    "start_date":  "2025-09-10T09:00:00",
    "end_date":    "2025-09-10T17:00:00"
  }' \
  "https://example.com/wp-json/author-events/v1/events"

Response 201 Created — returns the created event with a Location header pointing to the new resource.


PUT/PATCH /events/{id}

Updates an existing event. PUT expects all writeable fields; PATCH accepts any subset.

curl -u admin:app-password \
  -X PATCH \
  -H "Content-Type: application/json" \
  -d '{ "title": "Updated Title", "start_date": "2025-09-10T10:00:00", "end_date": "2025-09-10T18:00:00" }' \
  "https://example.com/wp-json/author-events/v1/events/42"

Response 200 OK — returns the full updated event object.


DELETE /events/{id}

Moves the event to the WordPress trash by default. Pass ?force=true to permanently delete.

# Move to trash
curl -u admin:app-password -X DELETE \
  "https://example.com/wp-json/author-events/v1/events/42"

# Permanent delete
curl -u admin:app-password -X DELETE \
  "https://example.com/wp-json/author-events/v1/events/42?force=true"

Response 200 OK

{
  "deleted": true,
  "previous": { "id": 42, "title": "Annual Author Summit", "..." : "..." }
}

Response Schema

Every event item conforms to the following shape. Fields marked readonly are never accepted on write.

Field Type Contexts Notes
id integer view, edit, embed readonly
title string view, edit, embed 1–200 chars, required on create
description string view, edit HTML allowed (wp_kses_post)
category string view, edit, embed Term name; auto-created
start_date string (ISO 8601) view, edit, embed required on create
end_date string (ISO 8601) view, edit, embed required on create; must be ≥ start_date
status string enum view, edit publish | draft | pending | private
date_created string (ISO 8601) view, edit readonly
date_modified string (ISO 8601) view, edit readonly

Error Responses

All errors follow the standard WordPress REST error envelope:

{
  "code":    "rest_invalid_param",
  "message": "end_date must be on or after start_date.",
  "data":    { "status": 400 }
}
Code HTTP status Trigger
rest_forbidden 401 / 403 Missing or insufficient capability
rest_invalid_param 400 Schema validation failure, bad date format, or end before start
rest_event_not_found 404 Post ID does not exist or wrong post type
rest_event_trashed 410 Post exists but is in trash
rest_cannot_delete 500 wp_delete_post() returned false

Running Tests

1. Install the WordPress test library

# Creates a test DB and downloads the WP test suite into /tmp/wordpress-tests-lib
bash bin/install-wp-tests.sh wordpress_test root '' localhost latest

2. Install dev dependencies

composer install

3. Run the suite

WP_TESTS_DIR=/tmp/wordpress-tests-lib vendor/bin/phpunit

With coverage report:

WP_TESTS_DIR=/tmp/wordpress-tests-lib vendor/bin/phpunit --coverage-html=coverage/

Test groups

Group Cases What is verified
Auth & authorisation 4 Anonymous and subscriber requests return 401/403 on all write endpoints
POST + DB verification 3 Admin creates event → HTTP 201, correct response shape, correct DB row, correct meta, correct taxonomy term
Date filter 4 ?date= returns only matching events, empty result for no-match, 400 for invalid format
Full CRUD 3 GET single, PUT update (with DB re-read), DELETE with force (with DB re-read)
Response contract 3 Pagination headers present, HATEOAS links present, required schema fields present

Roadmap

Priority Feature
High Object cache — wrap WP_Query results with wp_cache_get/set keyed on a query hash
High Audit log — record every API write (who, what, when) to a custom log CPT
Medium WP-CLI commandswp events create, wp events list --date=, wp events delete
Medium GitHub Actions CI — matrix test across PHP 8.0–8.3 × WP 6.0–latest; PHPCS on every PR
Medium Rate limiting — per-IP / per-token request throttle via rest_pre_dispatch, returns 429
Low Webhook dispatch — fire registered consumer URLs on create / update / delete
Low Cursor-based pagination — replace OFFSET paging with a keyed start_date cursor for large datasets
Low Import / Exportwp events import events.csv and wp events export --format=json

License

GPL v2 or later — see LICENSE.