package internal import ( "encoding/json" "fmt" "html/template" "io/ioutil" "net/http" "path/filepath" "strings" "time" rice "github.com/GeertJohan/go.rice" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" "github.com/james4k/fmatter" "github.com/julienschmidt/httprouter" log "github.com/sirupsen/logrus" "github.com/vcraescu/go-paginator" ) const ( MaxFailedLogins = 3 // By default 3 failed login attempts per 5 minutes ) func (s *Server) NotFoundHandler(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") http.Error(w, "Endpoint Not Found", http.StatusNotFound) return } ctx := NewContext(s.config, s.db, r) ctx.Title = "Page Not Found" w.WriteHeader(http.StatusNotFound) s.render("404", w, ctx) } type FrontMatter struct { Title string } // PageHandler ... func (s *Server) PageHandler(name string) httprouter.Handle { box := rice.MustFindBox("pages") mdTpl := box.MustString(fmt.Sprintf("%s.md", name)) return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := NewContext(s.config, s.db, r) md, err := RenderString(mdTpl, ctx) if err != nil { log.WithError(err).Errorf("error rendering page %s", name) ctx.Error = true ctx.Message = "Error loading help page! Please contact support." s.render("error", w, ctx) return } var frontmatter FrontMatter content, err := fmatter.Read([]byte(md), &frontmatter) if err != nil { log.WithError(err).Error("error parsing front matter") ctx.Error = true ctx.Message = "Error loading page! Please contact support." s.render("error", w, ctx) return } extensions := parser.CommonExtensions | parser.AutoHeadingIDs p := parser.NewWithExtensions(extensions) htmlFlags := html.CommonFlags opts := html.RendererOptions{ Flags: htmlFlags, Generator: "", } renderer := html.NewRenderer(opts) html := markdown.ToHTML(content, p, renderer) var title string if frontmatter.Title != "" { title = frontmatter.Title } else { title = strings.Title(name) } ctx.Title = title ctx.Page = name ctx.Content = template.HTML(html) s.render("page", w, ctx) } } // IndexHandler ... func (s *Server) IndexHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := NewContext(s.config, s.db, r) q := strings.TrimSpace(r.FormValue("q")) if q != "" { s.SearchHandler()(w, r, p) return } s.render("index", w, ctx) } } // AddHandler ... func (s *Server) AddHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := NewContext(s.config, s.db, r) url := NormalizeURL(strings.TrimSpace(r.FormValue("url"))) if url == "" && r.Method == http.MethodGet { s.render("add", w, ctx) return } if url == "" { ctx.Error = true ctx.Message = "Invalid URL" s.render("error", w, ctx) return } if err := s.crawler.Crawl(url); err != nil { ctx.Error = true ctx.Message = fmt.Sprintf("Error adding URL: %s", err) s.render("error", w, ctx) return } ctx.Error = false ctx.Message = "Successfully added url" s.render("error", w, ctx) } } // CacheHandler ... func (s *Server) CacheHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := NewContext(s.config, s.db, r) hash := p.ByName("hash") if hash == "" { http.Error(w, "Bad Request", http.StatusBadRequest) return } fn := filepath.Join(s.config.Data, cacheDir, fmt.Sprintf("%s.json", hash)) if !FileExists(fn) { ctx.Error = true ctx.Message = "Cached page not found!" s.render("404", w, ctx) return } var entry Entry data, err := ioutil.ReadFile(fn) if err != nil { log.WithError(err).Error("error reading cached entry") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if err := json.Unmarshal(data, &entry); err != nil { log.WithError(err).Error("error unmarshalling cached entry") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } ctx.CachedURL = entry.URL ctx.CachedTitle = entry.Title ctx.CachedContent = entry.Content s.render("cache", w, ctx) } } // SearchHandler ... func (s *Server) SearchHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := NewContext(s.config, s.db, r) q := strings.TrimSpace(r.FormValue("q")) if q == "" { ctx.Error = true ctx.Message = "Error empty search" s.render("error", w, ctx) return } p := SafeParseInt(r.FormValue("p"), 1) searchResults, err := s.indexer.Search(q, p) if err != nil { log.WithError(err).Error("error performing search") ctx.Error = true ctx.Message = "Error performing search, please try again later" s.render("error", w, ctx) return } log.Debug(searchResults) var results []Result for _, searchResult := range searchResults.Hits { result := Result{ ID: searchResult.ID, Title: searchResult.Fields["Title"].(string), Summary: searchResult.Fields["Summary"].(string), URL: searchResult.Fields["URL"].(string), Length: int(searchResult.Fields["Length"].(float64)), } results = append(results, result) } pager := paginator.New(IntAdapter{N: int(searchResults.Total)}, s.config.ResultsPerPage) pager.SetPage(p) ctx.Pager = &pager ctx.SearchQuery = q ctx.Results = results ctx.SearchTook = searchResults.Took.Truncate(time.Nanosecond) metrics.Counter("server", "queries").Inc() s.render("search", w, ctx) } }