docs: integrate API explorer into documentation pages
Phase 3 complete: Add API explorer to endpoint documentation - Add DocsLayoutWithExplorer template function - Update HTTPHandler.ShowAPIEndpoint to check authentication - Add GetAPIEndpointData method to docs handler - Include API explorer for all endpoint documentation - Explorer shows mock data to non-authenticated users - Explorer enables real API execution for logged-in users - Add legacy fallback for endpoints without explorer data
This commit is contained in:
@@ -231,3 +231,131 @@ func (h *DocsHandler) ListDocuments() ([]string, error) {
|
||||
|
||||
return docs, err
|
||||
}
|
||||
|
||||
// GetAPIEndpointData returns endpoint data for the API explorer
|
||||
func (h *DocsHandler) GetAPIEndpointData(endpointPath string) (*templates.EndpointInfo, error) {
|
||||
// Map endpoint path to data
|
||||
// For now, return static examples based on the endpoint path
|
||||
// In a full implementation, this would parse the markdown files
|
||||
|
||||
// Clean the path
|
||||
endpointPath = strings.TrimPrefix(endpointPath, "api/")
|
||||
endpointPath = strings.TrimSuffix(endpointPath, ".md")
|
||||
|
||||
// Map paths to endpoints
|
||||
endpoints := map[string]templates.EndpointInfo{
|
||||
"authentication/register": {
|
||||
Method: "POST",
|
||||
Path: "/api/auth/register",
|
||||
RequestBody: `{
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"password": "SecureP@ss123!",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}`,
|
||||
Response: `{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "d4f5g6h7...",
|
||||
"user": {
|
||||
"id": "uuid-here",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"role": "user",
|
||||
"theme": "tokyo-night",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
}`,
|
||||
Description: "Create a new user account",
|
||||
},
|
||||
"authentication/login": {
|
||||
Method: "POST",
|
||||
Path: "/api/auth/login",
|
||||
RequestBody: `{
|
||||
"email": "user@example.com",
|
||||
"password": "SecureP@ss123!"
|
||||
}`,
|
||||
Response: `{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "d4f5g6h7...",
|
||||
"user": {
|
||||
"id": "uuid-here",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"role": "user"
|
||||
}
|
||||
}`,
|
||||
Description: "Authenticate with email and password",
|
||||
},
|
||||
"authentication/refresh_token": {
|
||||
Method: "POST",
|
||||
Path: "/api/auth/refresh",
|
||||
RequestBody: `{
|
||||
"refresh_token": "d4f5g6h7..."
|
||||
}`,
|
||||
Response: `{
|
||||
"token": "new-jwt-token",
|
||||
"refresh_token": "new-refresh-token"
|
||||
}`,
|
||||
Description: "Obtain a new JWT token using a refresh token",
|
||||
},
|
||||
"authentication/logout": {
|
||||
Method: "POST",
|
||||
Path: "/api/auth/logout",
|
||||
RequestBody: `{}`,
|
||||
Response: `{}`,
|
||||
Description: "Invalidate the current JWT token",
|
||||
},
|
||||
"users/get_profile": {
|
||||
Method: "GET",
|
||||
Path: "/api/users/me",
|
||||
RequestBody: `{}`,
|
||||
Response: `{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"theme": "tokyo-night",
|
||||
"role": "user",
|
||||
"max_devices": 10,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}`,
|
||||
Description: "Retrieve the current authenticated user's profile",
|
||||
},
|
||||
"users/update_profile": {
|
||||
Method: "PUT",
|
||||
Path: "/api/users/me/profile",
|
||||
RequestBody: `{
|
||||
"first_name": "John",
|
||||
"last_name": "Smith"
|
||||
}`,
|
||||
Response: `{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"first_name": "John",
|
||||
"last_name": "Smith",
|
||||
"theme": "tokyo-night",
|
||||
"role": "user"
|
||||
}`,
|
||||
Description: "Update the current user's profile information",
|
||||
},
|
||||
"users/change_password": {
|
||||
Method: "PUT",
|
||||
Path: "/api/users/me/password",
|
||||
RequestBody: `{
|
||||
"current_password": "oldPassword",
|
||||
"new_password": "NewSecureP@ss123!"
|
||||
}`,
|
||||
Response: `{}`,
|
||||
Description: "Change the current user's password",
|
||||
},
|
||||
}
|
||||
|
||||
if endpoint, ok := endpoints[endpointPath]; ok {
|
||||
return &endpoint, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("endpoint not found: %s", endpointPath)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,72 @@ func (h *HTTPHandler) ShowDocumentation(c echo.Context) error {
|
||||
|
||||
// ShowAPIEndpoint shows a specific API endpoint documentation
|
||||
func (h *HTTPHandler) ShowAPIEndpoint(c echo.Context, endpointPath string) error {
|
||||
// Check if user is logged in
|
||||
isLoggedIn := false
|
||||
if userID := c.Get("user_id"); userID != nil {
|
||||
isLoggedIn = true
|
||||
}
|
||||
|
||||
// Get endpoint data for the explorer
|
||||
endpointInfo, err := h.docs.GetAPIEndpointData(endpointPath)
|
||||
if err != nil {
|
||||
// Endpoint not found in explorer data, fall back to old behavior
|
||||
return h.showLegacyAPIEndpoint(c, endpointPath, isLoggedIn)
|
||||
}
|
||||
|
||||
// Load the markdown documentation for this endpoint
|
||||
docPath := "api/" + endpointPath + ".md"
|
||||
doc, err := h.docs.LoadDocument(docPath)
|
||||
if err != nil {
|
||||
errorHTML := `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>API Endpoint Not Found - Bookhoard</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-gray-900 text-gray-100 min-h-screen flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold mb-4">API Endpoint Not Found</h1>
|
||||
<a href="/docs/api" class="text-blue-400 hover:underline">Return to API Documentation</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
return c.HTML(http.StatusNotFound, errorHTML)
|
||||
}
|
||||
|
||||
// Get navigation
|
||||
nav := h.docs.BuildNavigation()
|
||||
|
||||
// Create empty user (docs are public)
|
||||
user := templates.User{
|
||||
ID: "",
|
||||
Username: "",
|
||||
Email: "",
|
||||
Role: "",
|
||||
Theme: "tokyo-night",
|
||||
}
|
||||
|
||||
// Create API explorer data
|
||||
explorerData := templates.APIExplorerData{
|
||||
Endpoint: *endpointInfo,
|
||||
IsLoggedIn: isLoggedIn,
|
||||
}
|
||||
|
||||
// Render API endpoint page with explorer
|
||||
var buf bytes.Buffer
|
||||
err = templates.DocsLayoutWithExplorer(*nav, *doc, user, explorerData).Render(c.Request().Context(), &buf)
|
||||
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusInternalServerError, "Failed to render page")
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}
|
||||
|
||||
// showLegacyAPIEndpoint shows API endpoint documentation without explorer data
|
||||
func (h *HTTPHandler) showLegacyAPIEndpoint(c echo.Context, endpointPath string, isLoggedIn bool) error {
|
||||
endpoints := h.docs.GetAPIEndpoints()
|
||||
|
||||
// Find the endpoint
|
||||
|
||||
Reference in New Issue
Block a user