lieu/server/server.go

278 wiersze
7.5 KiB
Go

package server
import (
"database/sql"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"syscall"
"html/template"
"lieu/database"
"lieu/types"
"lieu/util"
)
type RequestHandler struct {
config types.Config
db *sql.DB
}
type TemplateView struct {
SiteName string
Data interface{}
}
type SearchData struct {
Query string
Title string
Site string
Pages []types.PageData
IsInternal bool
}
type IndexData struct {
Tagline string
Placeholder string
}
type ListData struct {
Title string
URLs []types.PageData
}
type AboutData struct {
DomainCount int
WebringName string
LastCrawl string
PageCount string
TermCount string
FilteredLink string
RingLink string
}
var templates = template.Must(template.ParseFiles(
"html/head.html", "html/nav.html", "html/footer.html",
"html/about.html", "html/index.html", "html/list.html", "html/search.html", "html/webring.html"))
const useURLTitles = true
func (h RequestHandler) searchRoute(res http.ResponseWriter, req *http.Request) {
var query string
var domain string
view := &TemplateView{}
var domains = []string{}
var nodomains = []string{}
var langs = []string{}
var queryFields = []string{}
if req.Method == http.MethodGet{
params := req.URL.Query()
if words, exists := params["q"]; exists && words[0] != "" {
query = words[0]
queryFields = strings.Fields(query)
}
// how to use: https://gist.github.com/cblgh/29991ba0a9e65cccbe14f4afd7c975f1
if parts, exists := params["site"]; exists && parts[0] != "" {
// make sure we only have the domain, and no protocol prefix
domain = strings.TrimPrefix(parts[0], "https://")
domain = strings.TrimPrefix(domain, "http://")
domain = strings.TrimSuffix(domain, "/")
domains = append(domains, domain)
}
// don't process if there are too many fields
if len(queryFields) <= 100 {
var newQueryFields []string;
for _, word := range queryFields {
// This could be more efficient by splitting arrays, but I'm going with the more readable version for now
if strings.HasPrefix(word, "site:") {
domains = append(domains, strings.TrimPrefix(word, "site:"))
} else if strings.HasPrefix(word, "-site:") {
nodomains = append(nodomains, strings.TrimPrefix(word, "-site:"))
} else if strings.HasPrefix(word, "lang:") {
langs = append(langs, strings.TrimPrefix(word, "lang:"))
} else {
newQueryFields = append(newQueryFields, word)
}
}
queryFields = newQueryFields;
}
}
if len(queryFields) == 0 || len(queryFields) > 100 || len(query) >= 8192 {
view.Data = IndexData{Tagline: h.config.General.Tagline, Placeholder: h.config.General.Placeholder}
h.renderView(res, "index", view)
return
}
var pages = database.SearchWords(h.db, util.Inflect(queryFields), true, domains, nodomains, langs)
if useURLTitles {
for i, pageData := range pages {
prettyURL, err := url.QueryUnescape(strings.TrimPrefix(strings.TrimPrefix(pageData.URL, "http://"), "https://"))
util.Check(err)
pageData.Title = prettyURL
pages[i] = pageData
}
}
view.Data = SearchData{
Title: "Results",
Query: query,
Site: domain,
Pages: pages,
IsInternal: true,
}
h.renderView(res, "search", view)
}
func (h RequestHandler) externalSearchRoute(res http.ResponseWriter, req *http.Request) {
var query string
view := &TemplateView{}
if req.Method == http.MethodGet {
params := req.URL.Query()
if words, exists := params["q"]; exists && words[0] != "" {
query = words[0]
}
}
pages := database.FulltextSearchWords(h.db, query)
if useURLTitles {
for i, pageData := range pages {
prettyURL, err := url.QueryUnescape(strings.TrimPrefix(strings.TrimPrefix(pageData.URL, "http://"), "https://"))
util.Check(err)
pageData.Title = prettyURL
pages[i] = pageData
}
}
view.Data = SearchData{
Title: "External Results",
Query: query,
Pages: pages,
IsInternal: false,
}
h.renderView(res, "search", view)
}
func (h RequestHandler) aboutRoute(res http.ResponseWriter, req *http.Request) {
view := &TemplateView{}
pageCount := util.Humanize(database.GetPageCount(h.db))
wordCount := util.Humanize(database.GetWordCount(h.db))
domainCount := database.GetDomainCount(h.db)
lastCrawl := database.GetLastCrawl(h.db)
view.Data = AboutData{
WebringName: h.config.General.Name,
DomainCount: domainCount,
PageCount: pageCount,
TermCount: wordCount,
LastCrawl: lastCrawl,
FilteredLink: "/filtered",
RingLink: h.config.General.URL,
}
h.renderView(res, "about", view)
}
func (h RequestHandler) filteredRoute(res http.ResponseWriter, req *http.Request) {
view := &TemplateView{}
var URLs []types.PageData
for _, domain := range util.ReadList(h.config.Crawler.BannedDomains, "\n") {
u, err := url.Parse(domain)
if err != nil {
continue
}
u.Scheme = "https"
p := types.PageData{Title: domain, URL: u.String()}
URLs = append(URLs, p)
}
view.Data = ListData{
Title: "Filtered Domains",
URLs: URLs,
}
h.renderView(res, "list", view)
}
func (h RequestHandler) randomRoute(res http.ResponseWriter, req *http.Request) {
link := database.GetRandomPage(h.db)
http.Redirect(res, req, link, http.StatusSeeOther)
}
func (h RequestHandler) randomExternalRoute(res http.ResponseWriter, req *http.Request) {
link := database.GetRandomExternalLink(h.db)
http.Redirect(res, req, link, http.StatusSeeOther)
}
func (h RequestHandler) webringRoute(res http.ResponseWriter, req *http.Request) {
http.Redirect(res, req, h.config.General.URL, http.StatusSeeOther)
}
func (h RequestHandler) renderView(res http.ResponseWriter, tmpl string, view *TemplateView) {
view.SiteName = h.config.General.Name
var errTemp error
if _, exists := os.LookupEnv("LIEU_DEV"); exists {
var templates = template.Must(template.ParseFiles(
"html/head.html", "html/nav.html", "html/footer.html",
"html/about.html", "html/index.html", "html/list.html", "html/search.html", "html/webring.html"))
errTemp = templates.ExecuteTemplate(res, tmpl+".html", view)
} else {
errTemp = templates.ExecuteTemplate(res, tmpl+".html", view)
}
if errors.Is(errTemp, syscall.EPIPE) {
fmt.Println("had a broken pipe, continuing")
} else {
util.Check(errTemp)
}
}
func WriteTheme(config types.Config) {
theme := config.Theme
// no theme is set, use the default
if theme.Foreground == "" || theme.Background == "" || theme.Links =="" {
return
}
colors := fmt.Sprintf(`/*This file will be automatically regenerated by lieu on startup if the theme colors are set in the configuration file*/
:root {
--primary: %s;
--secondary: %s;
--link: %s;
}`, theme.Foreground, theme.Background, theme.Links)
err := os.WriteFile("html/assets/theme.css", []byte(colors), 0644)
util.Check(err)
}
func Serve(config types.Config) {
WriteTheme(config)
db := database.InitDB(config.Data.Database)
handler := RequestHandler{config: config, db: db}
http.HandleFunc("/about", handler.aboutRoute)
http.HandleFunc("/", handler.searchRoute)
http.HandleFunc("/outgoing", handler.externalSearchRoute)
http.HandleFunc("/random/outgoing", handler.randomExternalRoute)
http.HandleFunc("/random", handler.randomRoute)
http.HandleFunc("/webring", handler.webringRoute)
http.HandleFunc("/filtered", handler.filteredRoute)
fileserver := http.FileServer(http.Dir("html/"))
http.Handle("/assets/", fileserver)
http.Handle("/robots.txt", fileserver)
portstr := fmt.Sprintf(":%d", config.General.Port)
fmt.Println("Listening on port: ", portstr)
http.ListenAndServe(portstr, nil)
}