package docs import ( "bytes" "fmt" "net/http" "strings" "bookhoard/internal/database" "bookhoard/templates" "github.com/google/uuid" "github.com/labstack/echo/v4" ) // HTTPHandler wraps the docs handler for Echo framework type HTTPHandler struct { docs *DocsHandler } // NewHTTPHandler creates a new docs HTTP handler func NewHTTPHandler(docsPath string) *HTTPHandler { return &HTTPHandler{ docs: NewDocsHandler(docsPath), } } // ShowDocumentation renders a documentation page func (h *HTTPHandler) ShowDocumentation(c echo.Context) error { // Get the document path from URL parameter docPath := c.Param("*") // Load the document doc, err := h.docs.LoadDocument(docPath) if err != nil { // Try to load as API endpoint if strings.HasPrefix(docPath, "api/") { return h.ShowAPIEndpoint(c, strings.TrimPrefix(docPath, "api/")) } // Return error page errorHTML := fmt.Sprintf(` Document Not Found - Bookhoard

Document Not Found

%s

Return to Documentation
`, err.Error()) return c.HTML(http.StatusNotFound, errorHTML) } // Get navigation nav := h.docs.BuildNavigation() // Check if user is authenticated via middleware var user templates.User if userID := c.Get("user"); userID != nil { dbUser := userID.(database.Users) var theme string if dbUser.Theme.Valid { theme = dbUser.Theme.String } else { theme = "tokyo-night" } user = templates.User{ ID: uuid.UUID(dbUser.ID.Bytes).String(), Username: dbUser.Username, Email: dbUser.Email, Role: dbUser.Role, Theme: theme, } } else { // Create empty user for unauthenticated users user = templates.User{ ID: "", Username: "", Email: "", Role: "", Theme: "tokyo-night", } } // Render documentation page var buf bytes.Buffer currentPath := c.Request().URL.Path err = templates.DocsLayout(*nav, *doc, user, currentPath).Render(c.Request().Context(), &buf) if err != nil { fmt.Printf("DOCS RENDER ERROR: %v\n", err) return c.HTML(http.StatusInternalServerError, "Failed to render page: "+err.Error()) } fmt.Printf("DOCS: Successfully rendered %s (%d bytes)\n", docPath, buf.Len()) return c.HTML(http.StatusOK, buf.String()) } // 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 := ` API Endpoint Not Found - Bookhoard

API Endpoint Not Found

Return to API Documentation
` return c.HTML(http.StatusNotFound, errorHTML) } // Get navigation nav := h.docs.BuildNavigation() // Check if user is authenticated via middleware var user templates.User if userID := c.Get("user"); userID != nil { dbUser := userID.(database.Users) var theme string if dbUser.Theme.Valid { theme = dbUser.Theme.String } else { theme = "tokyo-night" } user = templates.User{ ID: uuid.UUID(dbUser.ID.Bytes).String(), Username: dbUser.Username, Email: dbUser.Email, Role: dbUser.Role, Theme: theme, } } else { // Create empty user for unauthenticated users 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 currentPath := c.Request().URL.Path err = templates.DocsLayoutWithExplorer(*nav, *doc, user, explorerData, currentPath).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 var foundEndpoint *APIEndpoint for _, ep := range endpoints { if strings.HasSuffix(endpointPath, strings.TrimPrefix(ep.Path, "/api/")) { foundEndpoint = &ep break } } if foundEndpoint == nil { errorHTML := ` API Endpoint Not Found - Bookhoard

API Endpoint Not Found

Return to API Documentation
` return c.HTML(http.StatusNotFound, errorHTML) } // Get navigation nav := h.docs.BuildNavigation() // Check if user is authenticated via middleware var user templates.User currentPath := c.Request().URL.Path if userID := c.Get("user"); userID != nil { dbUser := userID.(database.Users) var theme string if dbUser.Theme.Valid { theme = dbUser.Theme.String } else { theme = "tokyo-night" } user = templates.User{ ID: uuid.UUID(dbUser.ID.Bytes).String(), Username: dbUser.Username, Email: dbUser.Email, Role: dbUser.Role, Theme: theme, } } else { // Create empty user for unauthenticated users user = templates.User{ ID: "", Username: "", Email: "", Role: "", Theme: "tokyo-night", } } // Render API endpoint page var buf bytes.Buffer err := templates.DocsLayout(*nav, templates.Document{ Title: foundEndpoint.Title, Content: fmt.Sprintf("

Documentation for %s %s

", foundEndpoint.Method, foundEndpoint.Path), TOC: []templates.TOCItem{}, Breadcrumb: []templates.BreadcrumbItem{ {Title: "Docs", URL: "/docs"}, {Title: "API Reference", URL: "/docs/api"}, {Title: foundEndpoint.Category, URL: "/docs/api"}, {Title: foundEndpoint.Title, URL: ""}, }, Category: "API Reference", SourceFile: "api", }, user, currentPath).Render(c.Request().Context(), &buf) if err != nil { return c.HTML(http.StatusInternalServerError, "Failed to render page") } return c.HTML(http.StatusOK, buf.String()) } // DocsHome redirects to the documentation index func (h *HTTPHandler) DocsHome(c echo.Context) error { return c.Redirect(http.StatusFound, "/docs/index.md") } // APIHome shows the API documentation home page func (h *HTTPHandler) APIHome(c echo.Context) error { endpoints := h.docs.GetAPIEndpoints() nav := h.docs.BuildNavigation() // Check if user is authenticated via middleware var user templates.User currentPath := c.Request().URL.Path if userID := c.Get("user"); userID != nil { dbUser := userID.(database.Users) var theme string if dbUser.Theme.Valid { theme = dbUser.Theme.String } else { theme = "tokyo-night" } user = templates.User{ ID: uuid.UUID(dbUser.ID.Bytes).String(), Username: dbUser.Username, Email: dbUser.Email, Role: dbUser.Role, Theme: theme, } } else { // Create empty user for unauthenticated users user = templates.User{ ID: "", Username: "", Email: "", Role: "", Theme: "tokyo-night", } } // Build API documentation content content := "

API Endpoints

Complete API reference for Bookhoard v1.0.

" apiDoc := &templates.Document{ Title: "API Reference", Content: content, TOC: []templates.TOCItem{}, Breadcrumb: []templates.BreadcrumbItem{ {Title: "Docs", URL: "/docs"}, {Title: "Developer", URL: "/docs/developer"}, {Title: "API Reference", URL: ""}, }, Category: "API Reference", SourceFile: "developer/api-reference.md", } var buf bytes.Buffer err := templates.DocsLayout(*nav, *apiDoc, user, currentPath).Render(c.Request().Context(), &buf) if err != nil { return c.HTML(http.StatusInternalServerError, "Failed to render page") } return c.HTML(http.StatusOK, buf.String()) } // Search searches through documentation func (h *HTTPHandler) Search(c echo.Context) error { query := c.QueryParam("q") if query == "" { return c.JSON(http.StatusBadRequest, map[string]string{ "error": "query parameter 'q' is required", }) } // Get all documents docs, err := h.docs.ListDocuments() if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{ "error": "failed to list documents", }) } // Simple search implementation var results []SearchResult queryLower := strings.ToLower(query) for _, docPath := range docs { doc, err := h.docs.LoadDocument(docPath) if err != nil { continue } // Check if title matches if strings.Contains(strings.ToLower(doc.Title), queryLower) { results = append(results, SearchResult{ Title: doc.Title, URL: "/docs/" + strings.TrimSuffix(doc.SourceFile, ".md"), Type: "Documentation", }) continue } // Check if content matches (simple substring search) contentLower := strings.ToLower(doc.Content) if strings.Contains(contentLower, queryLower) { results = append(results, SearchResult{ Title: doc.Title, URL: "/docs/" + strings.TrimSuffix(doc.SourceFile, ".md"), Type: "Documentation", }) } } return c.JSON(http.StatusOK, map[string]interface{}{ "results": results, "count": len(results), }) } // SearchResult represents a search result type SearchResult struct { Title string URL string Type string } // TryEndpoint executes an API endpoint (for the interactive explorer) func (h *HTTPHandler) TryEndpoint(c echo.Context) error { // This would execute the actual API request // For now, return a placeholder return c.JSON(http.StatusOK, map[string]interface{}{ "message": "API explorer not yet implemented", }) } // ServeSearchIndex serves the Lunr.js search index func (h *HTTPHandler) ServeSearchIndex(c echo.Context) error { index, err := h.docs.GenerateSearchIndex() if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{ "error": "failed to generate search index", }) } return c.JSON(http.StatusOK, index) }