Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Run frontend checks
run: pnpm check

- name: Validate OpenAPI contract
run: pnpm api:lint

test:
name: PHP ${{ matrix.php }}
runs-on: ubuntu-latest
Expand Down
9 changes: 9 additions & 0 deletions .spectral.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extends:
- spectral:oas

rules:
operation-description: off
operation-tags: error
operation-operationId: error
operation-operationId-unique: error
path-params: error
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ Start your web server and navigate to:
See:

- `docs/public-api.md` (HTTP + WebSocket + frontend entrypoints + reusable PHP APIs)
- `docs/openapi.yaml` (machine-readable OpenAPI 3.1 contract, rendered at `/api/docs`)
- `docs/api.md` (message reaction HTTP and WebSocket contracts)
- `docs/user-profiles.md` (profile settings, avatars, themes, and WebSocket presence)
- `docs/channels.md` (public channels, private DMs, APIs, WebSocket routing, and migration operations)
Expand Down
2 changes: 2 additions & 0 deletions app/Config/Routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
$routes->get('chat/svelte', 'Chat::svelte');

// Stable, machine-consumed API routes.
$routes->get('api/docs', 'ApiDocs::index');

$routes->group('api/v1', ['namespace' => 'App\Controllers\Api\V1'], static function (RouteCollection $routes): void {
$routes->get('messages', 'MessagesController::list', ['filter' => ['apiFormat', 'auth']]);
$routes->get('messages/search', 'MessagesController::search', ['filter' => ['apiFormat', 'auth']]);
Expand Down
2 changes: 2 additions & 0 deletions app/Config/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use App\Controllers\Api\V1\MessagesController;
use App\Controllers\Api\V1\PushSubscriptionsController;
use App\Controllers\Api\V1\ReactionsController;
use App\Controllers\ApiDocs;
use App\Controllers\AuditLog;
use App\Controllers\Auth;
use App\Controllers\Chat;
Expand Down Expand Up @@ -72,6 +73,7 @@ public static function controller(string $controllerClass): Controller

return match ($controllerClass) {
Home::class => new Home(),
ApiDocs::class => new ApiDocs(),
Chat::class => new Chat(static::chatRepository(), static::chatFormatter()),
MessagesController::class => new MessagesController(static::chatRepository(), static::chatFormatter()),
ChannelsController::class => new ChannelsController(static::channelRepository(), static::chatRepository(), static::userRepository()),
Expand Down
23 changes: 23 additions & 0 deletions app/Controllers/ApiDocs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Controllers;

use CodeIgniter\HTTP\ResponseInterface;

final class ApiDocs extends BaseController
{
public function index(): string|ResponseInterface
{
$spec = file_get_contents(ROOTPATH . 'docs/openapi.yaml');
if ($spec === false) {
return $this->response
->setStatusCode(500)
->setHeader('Content-Type', 'text/plain; charset=UTF-8')
->setBody('The OpenAPI specification is unavailable.');
}

return $this->respondWithView('api/docs', [
'openapiBase64' => base64_encode($spec),
], cacheable: true, cacheTime: 300);
}
}
21 changes: 21 additions & 0 deletions app/Views/api/docs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Interactive documentation for the CodeIgniter Chat v1 API">
<title>CodeIgniter Chat API documentation</title>
<?php helper('vite'); ?>
<?= vite_css_tag('src/js/api-docs.js') ?>
</head>
<body>
<main>
<div
id="swagger-ui"
data-openapi-base64="<?= esc($openapiBase64, 'attr') ?>"
aria-label="CodeIgniter Chat API documentation"
></div>
</main>
<?= vite_js_tag('src/js/api-docs.js') ?>
</body>
</html>
17 changes: 17 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ pnpm format

Please ensure the PHP and frontend checks pass before submitting a pull request. Biome covers repository JavaScript and TypeScript; framework-aware diagnostics are handled separately by `svelte-check` and `vue-tsc`.

### API contract

`docs/openapi.yaml` is the reviewed source of truth for every `/api/v1/*`
operation. When a pull request adds, removes, or changes a versioned endpoint,
update the OpenAPI operation, schemas, status codes, and concrete examples in
the same pull request.

Validate the contract locally with:

```bash
pnpm api:lint
```

This runs Spectral's OpenAPI 3.1 rules and compares the documented operations
with the CodeIgniter route group. CI rejects invalid specifications and route
drift. The rendered contract is available at `/api/docs` in a running app.

## Code Style Guidelines

- Follow PSR-12 coding standards (enforced by PHP-CS-Fixer)
Expand Down
Loading
Loading