diff --git a/.air.toml b/.air.toml index 8f9d6f6..6118030 100644 --- a/.air.toml +++ b/.air.toml @@ -9,10 +9,11 @@ args_bin = [ "-key tmp/key.gob", "-link https://distrikt-ni-st.de", "-log tmp/cpolis.log", + "-pdfs tmp/pdfs", "-pics tmp/pics", "-rss tmp/orientexpress_alle.rss", "-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'", - "-web web" + "-web web", ] bin = "./tmp/main" cmd = "go build -o ./tmp/main ./cmd/main.go" diff --git a/README.md b/README.md index 1ece5a2..f08a782 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ # cpolis -cpolis is an application written in Go to serve as the backend of the Orient Express magazine. \ No newline at end of file +cpolis is an application written in Go to serve as the backend of the Orient +Express magazine. + +## Installation + +You should have the following packages installed: + +- Go >= 1.22 +- MariaDB + +Enable and start the MariaDB service. + + sudo systemctl enable --now mariadb.service + +Set up a dedicated MariaDB user for cpolis. + diff --git a/cmd/backend/config.go b/cmd/backend/config.go index 02f9ffc..9ef36a6 100644 --- a/cmd/backend/config.go +++ b/cmd/backend/config.go @@ -17,6 +17,7 @@ type Config struct { KeyFile string Link string LogFile string + PDFDir string PicsDir string Port string RSSFile string @@ -29,6 +30,7 @@ func newConfig() *Config { DBName: "cpolis", KeyFile: "/var/www/cpolis/cpolis.key", LogFile: "/var/log/cpolis.log", + PDFDir: "/var/www/cpolis/pdfs", PicsDir: "/var/www/cpolis/pics", RSSFile: "/var/www/cpolis/cpolis.rss", WebDir: "/var/www/cpolis/web", @@ -79,6 +81,7 @@ func (c *Config) handleCliArgs() error { flag.StringVar(&c.KeyFile, "key", c.KeyFile, "key file") flag.StringVar(&c.Link, "link", c.Link, "Channel Link") flag.StringVar(&c.LogFile, "log", c.LogFile, "log file") + flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory") flag.StringVar(&c.PicsDir, "pics", c.PicsDir, "pictures directory") flag.StringVar(&c.RSSFile, "rss", c.RSSFile, "RSS file") flag.StringVar(&c.Title, "title", c.Title, "Channel title") @@ -96,6 +99,11 @@ func (c *Config) handleCliArgs() error { return fmt.Errorf("error finding absolute path for log file: %v", err) } + c.PDFDir, err = filepath.Abs(c.PDFDir) + if err != nil { + return fmt.Errorf("error finding absolute path for pdfs dir: %v", err) + } + c.PicsDir, err = filepath.Abs(c.PicsDir) if err != nil { return fmt.Errorf("error finding absolute path for pics dir: %v", err) diff --git a/cmd/backend/firebase.go b/cmd/backend/firebase.go new file mode 100644 index 0000000..e58e8b4 --- /dev/null +++ b/cmd/backend/firebase.go @@ -0,0 +1,44 @@ +package backend + +import ( + "context" + + firebase "firebase.google.com/go/v4" + "firebase.google.com/go/v4/auth" + "google.golang.org/api/option" +) + +type Client struct { + *auth.Client +} + +func NewClient() (*Client, error) { + var err error + client := new(Client) + + ctx := context.Background() + opt := option.WithCredentialsFile("path/to/serviceAccountKey.json") + + app, err := firebase.NewApp(ctx, nil, opt) + if err != nil { + return nil, err + } + + client.Client, err = app.Auth(ctx) + if err != nil { + return nil, err + } + + return client, nil +} + +func (c *Client) Verify(idToken string) (*auth.Token, error) { + ctx := context.Background() + + token, err := c.VerifyIDTokenAndCheckRevoked(ctx, idToken) + if err != nil { + return nil, err + } + + return token, nil +} diff --git a/cmd/calls/pdf.go b/cmd/calls/pdf.go new file mode 100644 index 0000000..7369b8e --- /dev/null +++ b/cmd/calls/pdf.go @@ -0,0 +1,44 @@ +package calls + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + b "streifling.com/jason/cpolis/cmd/backend" +) + +func ServePDFList(c *b.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if tokenIsVerified(w, r) { + files, err := os.ReadDir(c.PDFDir) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fileNames := make([]string, 0) + for _, file := range files { + fileNames = append(fileNames, file.Name()) + } + + w.Header().Set("Content-Type", "application/json") + if err = json.NewEncoder(w).Encode(fileNames); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + } +} + +func ServePDF(c *b.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if tokenIsVerified(w, r) { + http.ServeFile(w, r, fmt.Sprint(c.PDFDir, "/", r.PathValue("id"))) + } + } +} diff --git a/cmd/calls/rss.go b/cmd/calls/rss.go new file mode 100644 index 0000000..f98c9e1 --- /dev/null +++ b/cmd/calls/rss.go @@ -0,0 +1,15 @@ +package calls + +import ( + "net/http" + + b "streifling.com/jason/cpolis/cmd/backend" +) + +func ServeRSS(c *b.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if tokenIsVerified(w, r) { + http.ServeFile(w, r, c.RSSFile) + } + } +} diff --git a/cmd/calls/verification.go b/cmd/calls/verification.go new file mode 100644 index 0000000..2a25665 --- /dev/null +++ b/cmd/calls/verification.go @@ -0,0 +1,34 @@ +package calls + +import ( + "log" + "net/http" + + b "streifling.com/jason/cpolis/cmd/backend" +) + +// tokenIsVerified verifies that a request is authorized. It returns a bool. +func tokenIsVerified(w http.ResponseWriter, r *http.Request) bool { + idToken := r.Header.Get("Authorization") + if idToken == "" { + log.Println("Authorization header missing") + http.Error(w, "Authorization header missing", http.StatusUnauthorized) + return false + } + + client, err := b.NewClient() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return false + } + + _, err = client.Verify(idToken) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return false + } + + return true +} diff --git a/cmd/frontend/articles.go b/cmd/frontend/articles.go index 7b1bfba..9393897 100644 --- a/cmd/frontend/articles.go +++ b/cmd/frontend/articles.go @@ -23,29 +23,13 @@ const ( PreviewMode ) -func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { +func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - session, err := s.Get(r, "cookie") + session, err := getSession(w, r, c, s) if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - - session.Values["article"] = nil - if err = session.Save(r, w); err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) return } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int)) - } -} - -func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { type editorHTMLData struct { Title string Description string @@ -55,13 +39,6 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { Mode int } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - var data editorHTMLData if session.Values["article"] == nil { data = editorHTMLData{} @@ -84,11 +61,9 @@ func WriteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - session, err := s.Get(r, "cookie") + session, err := getSession(w, r, c, s) if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + return } session.Values["article"] = nil @@ -139,6 +114,11 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -178,21 +158,18 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) } } -func ShowUnpublishedArticles(c *b.Config, db *b.DB) http.HandlerFunc { +func ShowUnpublishedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + unpublishedArticles, err := db.GetCertainArticles(false, false) if err != nil { log.Println(err) @@ -208,19 +185,17 @@ func ShowUnpublishedArticles(c *b.Config, db *b.DB) http.HandlerFunc { func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + type htmlData struct { MyIDs map[int64]bool RejectedArticles []*b.Article } data := new(htmlData) - session, err := s.Get(r, "cookie") - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - data.RejectedArticles, err = db.GetCertainArticles(false, true) if err != nil { log.Println(err) @@ -243,6 +218,10 @@ func ShowRejectedArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerF func ReviewUnpublishedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + type htmlData struct { Title string Description string @@ -305,6 +284,10 @@ func ReviewUnpublishedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Hand func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + type htmlData struct { Selected map[int64]bool Article *b.Article @@ -352,6 +335,11 @@ func ReviewRejectedArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.Handler func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -359,13 +347,6 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - if err = db.AddArticleToCurrentIssue(id); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -402,6 +383,11 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -409,13 +395,6 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - if err = db.UpdateAttributes( &b.Attribute{Table: "articles", ID: id, AttName: "rejected", Value: true}, ); err != nil { @@ -430,8 +409,12 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { } } -func ShowCurrentArticles(c *b.Config, db *b.DB) http.HandlerFunc { +func ShowCurrentArticles(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + articles, err := db.GetCurrentIssueArticles() if err != nil { log.Println(err) @@ -444,8 +427,12 @@ func ShowCurrentArticles(c *b.Config, db *b.DB) http.HandlerFunc { } } -func UploadImage(c *b.Config) http.HandlerFunc { +func UploadImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + file, header, err := r.FormFile("article-image") if err != nil { log.Println(err) diff --git a/cmd/frontend/editor.go b/cmd/frontend/editor.go index 737c784..f133e62 100644 --- a/cmd/frontend/editor.go +++ b/cmd/frontend/editor.go @@ -7,8 +7,12 @@ import ( b "streifling.com/jason/cpolis/cmd/backend" ) -func CreateTag(c *b.Config) http.HandlerFunc { +func CreateTag(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-tag.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) } @@ -16,15 +20,13 @@ func CreateTag(c *b.Config) http.HandlerFunc { func AddTag(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - db.AddTag(r.PostFormValue("tag")) - - session, err := s.Get(r, "cookie") + session, err := getSession(w, r, c, s) if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + return } + db.AddTag(r.PostFormValue("tag")) + tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) diff --git a/cmd/frontend/images.go b/cmd/frontend/images.go index 7b77e5d..5718ee5 100644 --- a/cmd/frontend/images.go +++ b/cmd/frontend/images.go @@ -10,6 +10,10 @@ import ( func ServeImage(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + absFilepath, err := filepath.Abs(c.PicsDir) if err != nil { log.Println(err) diff --git a/cmd/frontend/issues.go b/cmd/frontend/issues.go index 043ca95..5361e48 100644 --- a/cmd/frontend/issues.go +++ b/cmd/frontend/issues.go @@ -10,19 +10,17 @@ import ( func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + if err := db.PublishLatestIssue(); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]) diff --git a/cmd/frontend/sessions.go b/cmd/frontend/sessions.go index 15245b1..514d9cf 100644 --- a/cmd/frontend/sessions.go +++ b/cmd/frontend/sessions.go @@ -90,11 +90,9 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func Logout(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - session, err := s.Get(r, "cookie") + session, err := getSession(w, r, c, s) if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + return } session.Options.MaxAge = -1 @@ -109,3 +107,22 @@ func Logout(c *b.Config, s *b.CookieStore) http.HandlerFunc { template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) } } + +func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + + session.Values["article"] = nil + if err = session.Save(r, w); err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int)) + } +} diff --git a/cmd/frontend/users.go b/cmd/frontend/users.go index 5b9b804..3eefa7f 100644 --- a/cmd/frontend/users.go +++ b/cmd/frontend/users.go @@ -30,8 +30,12 @@ func checkUserStrings(user *b.User) (string, int, bool) { } } -func CreateUser(c *b.Config) http.HandlerFunc { +func CreateUser(c *b.Config, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + tmpl, err := template.ParseFiles(c.WebDir + "/templates/add-user.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", nil) } @@ -39,6 +43,11 @@ func CreateUser(c *b.Config) http.HandlerFunc { func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + role, err := strconv.Atoi(r.PostFormValue("role")) if err != nil { log.Println(err) @@ -94,13 +103,6 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)) @@ -109,11 +111,9 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func EditSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - session, err := s.Get(r, "cookie") + session, err := getSession(w, r, c, s) if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + return } user, err := db.GetUser(session.Values["id"].(int64)) @@ -130,11 +130,9 @@ func EditSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - session, err := s.Get(r, "cookie") + session, err := getSession(w, r, c, s) if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) + return } userData := UserData{ @@ -200,8 +198,11 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var err error + if _, err := getSession(w, r, c, s); err != nil { + return + } + var err error htmlData := UserData{ User: &b.User{ UserName: r.PostFormValue("username"), @@ -275,7 +276,11 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var err error + session, err := getSession(w, r, c, s) + if err != nil { + return + } + type htmlData struct { Users map[int64]*b.User Action string @@ -289,21 +294,18 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - delete(data.Users, session.Values["id"].(int64)) tmpl, err := template.ParseFiles(c.WebDir + "/templates/show-all-users.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data) } } -func EditUser(c *b.Config, db *b.DB) http.HandlerFunc { +func EditUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if _, err := getSession(w, r, c, s); err != nil { + return + } + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -325,6 +327,11 @@ func EditUser(c *b.Config, db *b.DB) http.HandlerFunc { func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -394,13 +401,6 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { template.Must(tmpl, err).ExecuteTemplate(w, "page-content", userData) } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)) @@ -409,6 +409,11 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { func DeleteUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + session, err := getSession(w, r, c, s) + if err != nil { + return + } + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { log.Println(err) @@ -422,13 +427,6 @@ func DeleteUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc { return } - session, err := s.Get(r, "cookie") - if err != nil { - tmpl, err := template.ParseFiles(c.WebDir + "/templates/login.html") - msg := "Session nicht mehr gültig. Bitte erneut anmelden." - template.Must(tmpl, err).ExecuteTemplate(w, "page-content", msg) - } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl = template.Must(tmpl, err) tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)) diff --git a/cmd/frontend/verification.go b/cmd/frontend/verification.go new file mode 100644 index 0000000..9a62da5 --- /dev/null +++ b/cmd/frontend/verification.go @@ -0,0 +1,29 @@ +package frontend + +import ( + "errors" + "html/template" + "net/http" + + "github.com/gorilla/sessions" + b "streifling.com/jason/cpolis/cmd/backend" +) + +// getSession is used for verifying that the user is logged in and returns their session and an error. +func getSession(w http.ResponseWriter, r *http.Request, c *b.Config, s *b.CookieStore) (*sessions.Session, error) { + msg := "Keine gültige Session. Bitte erneut anmelden." + tmpl, tmplErr := template.ParseFiles(c.WebDir+"/templates/index.html", c.WebDir+"/templates/login.html") + + session, err := s.Get(r, "cookie") + if err != nil { + template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", msg) + return nil, err + } + + if session.IsNew { + template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", msg) + return session, errors.New("error: no existing session") + } + + return session, nil +} diff --git a/cmd/main.go b/cmd/main.go index a382324..c2ba0db 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,6 +7,7 @@ import ( "os" b "streifling.com/jason/cpolis/cmd/backend" + c "streifling.com/jason/cpolis/cmd/calls" f "streifling.com/jason/cpolis/cmd/frontend" ) @@ -48,13 +49,15 @@ func main() { http.FileServer(http.Dir(config.WebDir+"/static/")))) mux.HandleFunc("/", f.HomePage(config, db, store)) - mux.HandleFunc("GET /create-tag", f.CreateTag(config)) - mux.HandleFunc("GET /create-user", f.CreateUser(config)) + mux.HandleFunc("GET /create-tag", f.CreateTag(config, store)) + mux.HandleFunc("GET /create-user", f.CreateUser(config, store)) mux.HandleFunc("GET /edit-self", f.EditSelf(config, db, store)) - mux.HandleFunc("GET /edit-user/{id}", f.EditUser(config, db)) + mux.HandleFunc("GET /edit-user/{id}", f.EditUser(config, db, store)) mux.HandleFunc("GET /delete-user/{id}", f.DeleteUser(config, db, store)) mux.HandleFunc("GET /hub", f.ShowHub(config, db, store)) mux.HandleFunc("GET /logout", f.Logout(config, store)) + mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config)) + mux.HandleFunc("GET /pdf/{id}", c.ServePDF(config)) mux.HandleFunc("GET /pics/{pic}", f.ServeImage(config, store)) mux.HandleFunc("GET /publish-article/{id}", f.PublishArticle(config, db, store)) mux.HandleFunc("GET /publish-issue", f.PublishLatestIssue(config, db, store)) @@ -62,11 +65,11 @@ func main() { mux.HandleFunc("GET /rejected-articles", f.ShowRejectedArticles(config, db, store)) mux.HandleFunc("GET /review-rejected-article/{id}", f.ReviewRejectedArticle(config, db, store)) mux.HandleFunc("GET /review-unpublished-article/{id}", f.ReviewUnpublishedArticle(config, db, store)) - mux.HandleFunc("GET /rss", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, config.RSSFile) }) + mux.HandleFunc("GET /rss", c.ServeRSS(config)) mux.HandleFunc("GET /show-all-users-edit", f.ShowAllUsers(config, db, store, "edit-user")) mux.HandleFunc("GET /show-all-users-delete", f.ShowAllUsers(config, db, store, "delete-user")) - mux.HandleFunc("GET /this-issue", f.ShowCurrentArticles(config, db)) - mux.HandleFunc("GET /unpublished-articles", f.ShowUnpublishedArticles(config, db)) + mux.HandleFunc("GET /this-issue", f.ShowCurrentArticles(config, db, store)) + mux.HandleFunc("GET /unpublished-articles", f.ShowUnpublishedArticles(config, db, store)) mux.HandleFunc("GET /write-article", f.WriteArticle(config, db, store)) mux.HandleFunc("POST /add-first-user", f.AddFirstUser(config, db, store)) @@ -77,7 +80,7 @@ func main() { mux.HandleFunc("POST /submit-article", f.SubmitArticle(config, db, store)) mux.HandleFunc("POST /update-self", f.UpdateSelf(config, db, store)) mux.HandleFunc("POST /update-user/{id}", f.UpdateUser(config, db, store)) - mux.HandleFunc("POST /upload-image", f.UploadImage(config)) + mux.HandleFunc("POST /upload-image", f.UploadImage(config, store)) log.Fatalln(http.ListenAndServe(config.Port, mux)) } diff --git a/go.mod b/go.mod index 7fd87e0..f50229c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module streifling.com/jason/cpolis go 1.22.0 require ( + firebase.google.com/go/v4 v4.14.1 git.streifling.com/jason/rss v0.1.2 github.com/BurntSushi/toml v1.3.2 github.com/go-sql-driver/mysql v1.7.1 @@ -10,14 +11,49 @@ require ( github.com/gorilla/sessions v1.2.2 github.com/microcosm-cc/bluemonday v1.0.26 github.com/yuin/goldmark v1.7.0 - golang.org/x/crypto v0.14.0 - golang.org/x/term v0.17.0 + golang.org/x/crypto v0.21.0 + golang.org/x/term v0.18.0 + google.golang.org/api v0.170.0 ) require ( + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute v1.24.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/firestore v1.15.0 // indirect + cloud.google.com/go/iam v1.1.7 // indirect + cloud.google.com/go/longrunning v0.5.5 // indirect + cloud.google.com/go/storage v1.40.0 // indirect + github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.17.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/appengine/v2 v2.0.2 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/grpc v1.62.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index 84da03e..d2d9396 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,92 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= +cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= +cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= +cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= +cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= +firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= +firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= git.streifling.com/jason/rss v0.1.2 h1:UB3UHJXMt5WDDh9y8n0Z6nS1XortbPXjEr7QZTdovY4= git.streifling.com/jason/rss v0.1.2/go.mod h1:gpZF0nZbQSstMpyHD9DTAvlQEG7v4pjO5c7aIMWM4Jg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= +github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= @@ -18,13 +95,138 @@ github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTj github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48= +google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk= +google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= +google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/web/static/js/htmx.min.js b/web/static/js/htmx.min.js deleted file mode 100644 index 47eb70f..0000000 --- a/web/static/js/htmx.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:F,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(e,t){var r=dr(e,t||"post");return r.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Br,logAll:V,logNone:j,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.10"};var r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R};var w=["get","post","put","delete","patch"];var i=w.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var S=e("head"),q=e("title"),H=e("svg",true);function e(e,t=false){return new RegExp(`<${e}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${e}>`,t?"gim":"im")}function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function L(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=L(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function A(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function a(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function N(e){return/
"+n+"",0);return i.querySelector("template").content}switch(r){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return a("