startpage/main.go

220 lines
4.8 KiB
Go

package main
import (
"embed"
"encoding/json"
"errors"
"fmt"
"html/template"
"net/http"
"net/url"
"strings"
"time"
"git.milar.in/milarin/advsql"
"git.milar.in/milarin/channel"
"git.milar.in/milarin/envvars/v2"
"github.com/gorilla/mux"
)
var (
//go:embed templates/*
TemplateFS embed.FS
//go:embed static/*
StaticFS embed.FS
Templates *template.Template
)
func main() {
advsql.InitMysqlDatabase(
Database,
envvars.String("DB_HOST", ""),
envvars.Uint16("DB_PORT", 3306),
envvars.String("DB_USER", ""),
envvars.String("DB_PASS", ""),
envvars.String("DB_BASE", "startpage"),
)
defer Database.Close()
if tmpl, err := template.New("homepage").Funcs(tmplFuncs).ParseFS(TemplateFS, "templates/*"); err == nil {
Templates = tmpl
} else {
panic(err)
}
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/customize", customize)
r.HandleFunc("/save-changes", saveChanges)
r.HandleFunc("/search", search)
r.PathPrefix("/static/").Handler(http.FileServer(http.FS(StaticFS)))
go func() {
for range time.Tick(time.Hour) {
if err := DeleteSessionsByExpirationDateBefore(time.Now()); err != nil {
fmt.Println("could not delete expired sessions", err)
}
}
}()
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", envvars.String("HTTP_INTF", ""), envvars.Uint16("HTTP_PORT", 80)), r); err != nil {
panic(err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
return
}
data := &TmplData{
text: GetText(r),
Bookmarks: channel.ToSliceDeref(GetVisibleBookmarksBySessionIdOrdered(session.ID)),
Settings: GetSettingsBySessionID(session.ID),
}
if err := Templates.ExecuteTemplate(w, "index.html", data); err != nil {
panic(err)
}
}
func search(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
return
}
settings := GetSettingsBySessionID(session.ID)
if err := r.ParseForm(); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
query := r.Form.Get("query")
if uri, err := ParseURI(query); err == nil {
// url
w.Header().Add("Location", uri.String())
w.WriteHeader(http.StatusMovedPermanently)
} else {
// search string
w.Header().Add("Location", fmt.Sprintf(settings.Search, query))
w.WriteHeader(http.StatusMovedPermanently)
}
}
func customize(w http.ResponseWriter, r *http.Request) {
session, err := GetSession(w, r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
return
}
text := GetText(r)
data := &CustomizeData{
text: text,
Settings: GetSettingsBySessionID(session.ID),
Bookmarks: channel.ToSliceDeref(channel.MapSuccessive(GetBookmarksBySessionIdOrdered(session.ID), func(b *Bookmark) *BookmarkData {
return &BookmarkData{
Bookmark: b,
text: text,
}
})),
}
if err := Templates.ExecuteTemplate(w, "customize.html", data); err != nil {
panic(err)
}
}
func saveChanges(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
session, err := GetSession(w, r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
return
}
sessionData := &SessionData{}
err = json.NewDecoder(r.Body).Decode(&sessionData)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
for _, bookmark := range sessionData.Bookmarks {
uri, err := url.Parse(bookmark.Link)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
if uri.Scheme == "" {
uri.Scheme = "https"
}
bookmark.Link = uri.String()
}
Reorder(sessionData.Bookmarks)
if err := DeleteBookmarksBySessionID(session.ID); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
for _, bookmark := range sessionData.Bookmarks {
bookmark.SessionID = session.ID
if err := InsertBookmark(&bookmark); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
sessionData.Settings.SessionID = session.ID
if err := UpdateSettings(sessionData.Settings); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func ParseURI(uri string) (*url.URL, error) {
if !strings.HasPrefix(uri, "http://") && !strings.HasPrefix(uri, "https://") {
uri = "https://" + uri
}
ret, err := url.ParseRequestURI(uri)
if err != nil {
return nil, err
}
splits := strings.Split(ret.Hostname(), ".")
if len(splits) <= 1 {
return nil, errors.New("hostname doesn't have a TLD")
}
if _, ok := TLDs[splits[len(splits)-1]]; !ok {
return nil, errors.New("invalid top level domain")
}
return ret, nil
}