package controllers import ( "io/ioutil" "net/http" "path/filepath" "strings" "github.com/gin-gonic/gin" ) type DocsController struct { DocsPath string } func NewDocsController(docsPath string) *DocsController { return &DocsController{ DocsPath: docsPath, } } // GetDocFile serves a specific documentation file func (dc *DocsController) GetDocFile(c *gin.Context) { // Get the requested file path from the URL docPath := c.Param("filepath") // Security: Prevent directory traversal if strings.Contains(docPath, "..") { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file path"}) return } // Build full path fullPath := filepath.Join(dc.DocsPath, docPath) // Check if file exists and is a markdown file if !strings.HasSuffix(fullPath, ".md") { c.JSON(http.StatusBadRequest, gin.H{"error": "Only markdown files are allowed"}) return } // Read the file content, err := ioutil.ReadFile(fullPath) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Documentation file not found"}) return } // Return the content as plain text c.Header("Content-Type", "text/markdown; charset=utf-8") c.String(http.StatusOK, string(content)) } // ListDocFiles returns a list of available documentation files func (dc *DocsController) ListDocFiles(c *gin.Context) { files, err := ioutil.ReadDir(dc.DocsPath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read documentation directory"}) return } var docFiles []map[string]interface{} for _, file := range files { if !file.IsDir() && strings.HasSuffix(file.Name(), ".md") { docFiles = append(docFiles, map[string]interface{}{ "name": file.Name(), "path": "/DOCS/" + file.Name(), "size": file.Size(), "modified_at": file.ModTime(), }) } } c.JSON(http.StatusOK, gin.H{ "files": docFiles, "total": len(docFiles), }) } // SearchDocs searches through documentation files func (dc *DocsController) SearchDocs(c *gin.Context) { query := c.Query("q") if query == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Search query is required"}) return } query = strings.ToLower(query) files, err := ioutil.ReadDir(dc.DocsPath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read documentation directory"}) return } var results []map[string]interface{} for _, file := range files { if file.IsDir() || !strings.HasSuffix(file.Name(), ".md") { continue } fullPath := filepath.Join(dc.DocsPath, file.Name()) content, err := ioutil.ReadFile(fullPath) if err != nil { continue } contentLower := strings.ToLower(string(content)) nameLower := strings.ToLower(file.Name()) // Check if query matches filename or content if strings.Contains(nameLower, query) || strings.Contains(contentLower, query) { // Find context around match index := strings.Index(contentLower, query) start := 0 end := len(content) if index > 100 { start = index - 100 } if index+len(query)+200 < len(content) { end = index + len(query) + 200 } excerpt := string(content[start:end]) results = append(results, map[string]interface{}{ "name": file.Name(), "path": "/DOCS/" + file.Name(), "excerpt": excerpt, "matches": strings.Count(contentLower, query), }) } } c.JSON(http.StatusOK, gin.H{ "results": results, "total": len(results), "query": query, }) }