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: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ When dispatching subagents:

- **Project Docs**: `docs/` (Sphinx RST format)
- **Docker Guide**: `docs/docker-setup.md`
- **API Docs**: http://localhost:8000/api/swagger (when running)
- **API Docs (Scalar)**: http://localhost:8000/api/scalar (default, modern UI)
- **API Docs (Swagger)**: http://localhost:8000/api/swagger (fallback)
- **External**:
- discord.py: https://discordpy.readthedocs.io/
- Litestar: https://docs.litestar.dev/
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ make docker-up

# 4. Access the application
# API: http://localhost:8000
# API Docs: http://localhost:8000/api/swagger
# API Docs (Scalar): http://localhost:8000/api/scalar
# API Docs (Swagger): http://localhost:8000/api/swagger
```

**📚 Full Docker Guide**: See [docs/docker-setup.md](docs/docker-setup.md) for comprehensive documentation including:
Expand Down
24 changes: 24 additions & 0 deletions docs/web/api/lib/openapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,29 @@ openapi

OpenAPI config

The Byte Bot API uses `Scalar <https://scalar.com/>`_ as the default OpenAPI documentation UI,
with Swagger UI available as a fallback option.

Accessing the API Documentation
--------------------------------

* **Scalar UI (Default)**: http://localhost:8000/api/scalar
* **Swagger UI (Fallback)**: http://localhost:8000/api/swagger
* **OpenAPI Schema**: http://localhost:8000/api/openapi.json

Custom Theme
------------

The Scalar UI uses a custom theme with Byte brand colors:

* Primary: ``#42b1a8`` (Byte Teal)
* Secondary: ``#7bcebc`` (Byte Blue)
* Accent: ``#abe6d2`` (Byte Light Blue)

The theme supports both light and dark modes with automatic detection.

Configuration
-------------

.. automodule:: byte_api.lib.openapi
:members:
107 changes: 107 additions & 0 deletions services/api/src/byte_api/domain/web/resources/css/scalar-theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* Byte Brand Custom Theme for Scalar OpenAPI Documentation */

/* Light Mode Variables */
:root {
/* Primary accent color - Byte Teal */
--scalar-color-accent: #42b1a8;

/* Background colors */
--scalar-background-1: #ebebe9; /* byte-white base */
--scalar-background-2: #ffffff; /* slightly lighter */
--scalar-background-3: #d4d4d2; /* slightly darker */

/* Text colors */
--scalar-color-1: #0c0c0c; /* byte-dark primary text */
--scalar-color-2: #3c3c3c; /* medium text */
--scalar-color-3: #6c6c6c; /* light text */

/* Border color - Byte Blue secondary */
--scalar-border-color: #7bcebc;

/* Sidebar */
--scalar-sidebar-background-1: #42b1a8; /* byte-teal */
--scalar-sidebar-background-2: #7bcebc; /* byte-blue */
--scalar-sidebar-color-1: #ebebe9; /* light text on dark sidebar */
--scalar-sidebar-color-2: #ffffff; /* white text */
--scalar-sidebar-color-active: #abe6d2; /* byte-light-blue for active items */
--scalar-sidebar-border-color: #7bcebc;

/* Status colors from DaisyUI theme */
--scalar-color-green: #059669; /* success */
--scalar-color-yellow: #d4a35a; /* byte-orange warning */
--scalar-color-red: #fc7054; /* byte-red error */
--scalar-color-blue: #7bcebc; /* byte-blue info */

/* Additional accents */
--scalar-button-1: #42b1a8;
--scalar-button-1-hover: #389e96;
--scalar-button-2: #7bcebc;
--scalar-button-2-hover: #6ab8a8;
}

/* Dark Mode Variables */
@media (prefers-color-scheme: dark) {
:root {
/* Primary accent - keep teal */
--scalar-color-accent: #42b1a8;

/* Background colors - inverted */
--scalar-background-1: #0c0c0c; /* byte-dark */
--scalar-background-2: #1a1a1a; /* slightly lighter */
--scalar-background-3: #2a2a2a; /* lighter still */

/* Text colors - inverted */
--scalar-color-1: #ebebe9; /* byte-white for text */
--scalar-color-2: #d4d4d2; /* medium text */
--scalar-color-3: #a4a4a2; /* light text */

/* Border color */
--scalar-border-color: #42b1a8;

/* Sidebar - darker in dark mode */
--scalar-sidebar-background-1: #0c0c0c;
--scalar-sidebar-background-2: #1a1a1a;
--scalar-sidebar-color-1: #ebebe9;
--scalar-sidebar-color-2: #ffffff;
--scalar-sidebar-color-active: #42b1a8;
--scalar-sidebar-border-color: #42b1a8;

/* Status colors remain consistent */
--scalar-color-green: #059669;
--scalar-color-yellow: #d4a35a;
--scalar-color-red: #fc7054;
--scalar-color-blue: #7bcebc;

/* Buttons in dark mode */
--scalar-button-1: #42b1a8;
--scalar-button-1-hover: #52c1b8;
--scalar-button-2: #7bcebc;
--scalar-button-2-hover: #8bdccc;
}
}

/* Force dark mode when .dark class is present */
.dark {
--scalar-color-accent: #42b1a8;
--scalar-background-1: #0c0c0c;
--scalar-background-2: #1a1a1a;
--scalar-background-3: #2a2a2a;
--scalar-color-1: #ebebe9;
--scalar-color-2: #d4d4d2;
--scalar-color-3: #a4a4a2;
--scalar-border-color: #42b1a8;
--scalar-sidebar-background-1: #0c0c0c;
--scalar-sidebar-background-2: #1a1a1a;
--scalar-sidebar-color-1: #ebebe9;
--scalar-sidebar-color-2: #ffffff;
--scalar-sidebar-color-active: #42b1a8;
--scalar-sidebar-border-color: #42b1a8;
--scalar-color-green: #059669;
--scalar-color-yellow: #d4a35a;
--scalar-color-red: #fc7054;
--scalar-color-blue: #7bcebc;
--scalar-button-1: #42b1a8;
--scalar-button-1-hover: #52c1b8;
--scalar-button-2: #7bcebc;
--scalar-button-2-hover: #8bdccc;
}
7 changes: 6 additions & 1 deletion services/api/src/byte_api/lib/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from __future__ import annotations

from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
from litestar.openapi.spec import Contact

from byte_api.lib import settings
from byte_api.lib.scalar_theme import create_scalar_plugin

__all__ = ("config",)

Expand All @@ -17,8 +19,11 @@
version=settings.openapi.VERSION,
contact=Contact(name=settings.openapi.CONTACT_NAME, email=settings.openapi.CONTACT_EMAIL),
use_handler_docstrings=True,
root_schema_site="swagger",
path=settings.openapi.PATH,
render_plugins=[
create_scalar_plugin(),
SwaggerRenderPlugin(path="/swagger"),
],
)
"""OpenAPI config for the project.
See :class:`OpenAPISettings <.settings.OpenAPISettings>` for configuration.
Expand Down
19 changes: 19 additions & 0 deletions services/api/src/byte_api/lib/scalar_theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Custom Scalar OpenAPI theme with Byte branding."""

from __future__ import annotations

from litestar.openapi.plugins import ScalarRenderPlugin

__all__ = ("create_scalar_plugin",)


def create_scalar_plugin() -> ScalarRenderPlugin:
"""Create a Scalar plugin with custom Byte branding.

Returns:
Configured ScalarRenderPlugin with custom CSS
"""
return ScalarRenderPlugin(
path="/scalar",
css_url="/static/css/scalar-theme.css",
)
21 changes: 15 additions & 6 deletions tests/unit/api/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"test_openapi_config_created",
"test_openapi_contact_info",
"test_openapi_includes_all_endpoints",
"test_openapi_render_plugins_configured",
"test_openapi_scalar_primary_plugin",
"test_openapi_schema_generation",
"test_openapi_security_schemes",
"test_openapi_servers_configured",
Expand Down Expand Up @@ -58,9 +60,13 @@ def test_openapi_uses_handler_docstrings() -> None:
assert openapi.config.use_handler_docstrings is True


def test_openapi_root_schema_site() -> None:
"""Test OpenAPI root schema site is set to Swagger."""
assert openapi.config.root_schema_site == "swagger"
def test_openapi_render_plugins_configured() -> None:
"""Test OpenAPI render plugins are configured with Scalar and Swagger."""
assert openapi.config.render_plugins is not None
assert len(openapi.config.render_plugins) >= 2
plugin_types = [type(plugin).__name__ for plugin in openapi.config.render_plugins]
assert "ScalarRenderPlugin" in plugin_types
assert "SwaggerRenderPlugin" in plugin_types


@pytest.mark.asyncio
Expand Down Expand Up @@ -330,9 +336,12 @@ def test_openapi_use_handler_docstrings_true() -> None:
assert openapi.config.use_handler_docstrings is True


def test_openapi_root_schema_site_swagger() -> None:
"""Test root schema site is Swagger."""
assert openapi.config.root_schema_site == "swagger"
def test_openapi_scalar_primary_plugin() -> None:
"""Test Scalar is the primary render plugin."""
assert openapi.config.render_plugins is not None
assert len(openapi.config.render_plugins) > 0
first_plugin_type = type(openapi.config.render_plugins[0]).__name__
assert first_plugin_type == "ScalarRenderPlugin"


def test_openapi_config_immutable() -> None:
Expand Down
Loading