package main
import (
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"gitlab.com/golang-commonmark/markdown"
"p83.nl/go/ekster/pkg/util"
"p83.nl/go/indieauth"
)
var (
mp PagesRepository
)
const (
ClientID = "https://wiki.p83.nl/"
RedirectURI = "https://wiki.p83.nl/auth/callback"
)
// Page
type Page struct {
Content string
}
type DiffPage struct {
Content string
Diff template.HTML
}
type Revision struct {
Version string
Page DiffPage
Summary string
}
type Change struct {
Page string
Date time.Time
EndDate time.Time
Body string
Count int
DateSummary string
TimeSummary string
}
type PagesRepository interface {
Get(p string) Page
Save(p string, page Page, summary, author string) error
Exist(p string) bool
PageHistory(p string) ([]Revision, error)
RecentChanges() ([]Change, error)
}
type indexPage struct {
Session *Session
Title string
Name string
Content template.HTML
}
type editPage struct {
Session *Session
Title string
Content string
Name string
Editor template.HTML
}
type historyPage struct {
Session *Session
Title string
Name string
History []Revision
}
type recentPage struct {
Session *Session
Title string
Name string
Recent []Change
}
type indexHandler struct{}
type saveHandler struct{}
type editHandler struct{}
type historyHandler struct{}
type recentHandler struct{}
type authHandler struct{}
func (*authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sess, err := NewSession(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer func() {
if err := sess.Flush(); err != nil {
log.Println(err)
}
}()
if r.Method == http.MethodGet {
if r.URL.Path == "/auth/login" {
t, err := template.ParseFiles("templates/layout.html", "templates/login.html")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
err = t.Execute(w, nil)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
} else if r.URL.Path == "/auth/callback" {
log.Printf("%+v\n", sess)
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
if state != sess.State {
log.Printf("mismatched state: %s != %s", state, sess.State)
http.Error(w, "mismatched state", 500)
return
}
authURL := sess.AuthorizationEndpoint
verified, response, err := indieauth.VerifyAuthCode(ClientID, code, RedirectURI, authURL)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
if !verified {
http.Redirect(w, r, "/auth/login", 302)
return
}
sess.Me = response.Me
sess.LoggedIn = true
sess.State = ""
http.Redirect(w, r, sess.NextURI, 302)
return
} else if r.URL.Path == "/auth/logout" {
t, err := template.ParseFiles("templates/layout.html", "templates/logout.html")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
err = t.Execute(w, nil)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
} else if r.Method == http.MethodPost {
if r.URL.Path == "/auth/login" {
r.ParseForm()
urlString := r.PostFormValue("url")
u, err := url.Parse(urlString)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
endpoints, err := indieauth.GetEndpoints(u)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
authURL, err := url.Parse(endpoints.AuthorizationEndpoint)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
state := util.RandStringBytes(32)
sess.AuthorizationEndpoint = authURL.String()
sess.Me = urlString
sess.LoggedIn = false
sess.RedirectURI = RedirectURI
sess.NextURI = "/"
sess.State = state
newURL := indieauth.CreateAuthenticationURL(*authURL, urlString, ClientID, RedirectURI, state)
http.Redirect(w, r, newURL, 302)
return
} else if r.URL.Path == "/auth/logout" {
sess.LoggedIn = false
sess.Me = ""
sess.AuthorizationEndpoint = ""
http.Redirect(w, r, "/", 302)
return
}
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *historyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sess, err := NewSession(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer func() {
if err := sess.Flush(); err != nil {
log.Println(err)
}
}()
if !sess.LoggedIn {
http.Redirect(w, r, "/", http.StatusFound)
return
}
r.ParseForm()
page := r.URL.Path[9:]
if page == "" {
page = "Home"
}
history, err := mp.PageHistory(page)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
funcs := template.FuncMap{
"historyIndex": func(x int, history []Revision) int { return len(history) - x },
}
t, err := template.New("layout.html").Funcs(funcs).ParseFiles("templates/layout.html", "templates/history.html")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
err = t.Execute(w, historyPage{
Session: sess,
Title: "History of " + strings.Replace(page, "_", " ", -1),
Name: page,
History: history,
})
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
func (h *saveHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sess, err := NewSession(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer func() {
if err := sess.Flush(); err != nil {
log.Println(err)
}
}()
if !sess.LoggedIn {
http.Redirect(w, r, "/", http.StatusFound)
return
}
err = r.ParseForm()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
isJson := r.PostForm.Get("json") == "1"
page := r.PostForm.Get("p")
summary := r.PostForm.Get("summary")
pageData := Page{Content: r.PostForm.Get("content")}
err = mp.Save(page, pageData, summary, sess.Me)
if err != nil {
log.Println(err)
}
if isJson {
fmt.Print(w, "{}")
} else {
http.Redirect(w, r, "/"+page, http.StatusFound)
}
}
func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sess, err := NewSession(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer func() {
if err := sess.Flush(); err != nil {
log.Println(err)
}
}()
if !sess.LoggedIn {
http.Redirect(w, r, "/", http.StatusFound)
return
}
page := r.URL.Path[6:]
if page == "" {
page = "Home"
}
pageText := mp.Get(page).Content
jsonEditor := pageText[0] == '{'
var editor template.HTML
if jsonEditor {
editor, err = renderEditor(page, pageText, "json")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
} else {
editor = `