mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-25 08:58:50 +00:00
refactor: replace gorilla/mux with echo
This commit is contained in:
parent
85e7c2765a
commit
9848871630
19 changed files with 409 additions and 311 deletions
13
go.mod
13
go.mod
|
@ -4,11 +4,12 @@ go 1.21.6
|
|||
|
||||
require (
|
||||
github.com/go-playground/validator/v10 v10.18.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/gosimple/slug v1.13.1
|
||||
github.com/labstack/echo/v4 v4.11.4
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/temporalio/ui-server/v2 v2.23.0
|
||||
go.temporal.io/sdk v1.26.0-rc.2
|
||||
|
@ -53,6 +54,7 @@ require (
|
|||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/gocql/gocql v1.5.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.7.0-rc.1 // indirect
|
||||
|
@ -78,10 +80,8 @@ require (
|
|||
github.com/jmoiron/sqlx v1.3.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/labstack/echo/v4 v4.9.1 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
@ -94,7 +94,6 @@ require (
|
|||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pborman/uuid v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
|
@ -121,7 +120,7 @@ require (
|
|||
github.com/uber-common/bark v1.3.0 // indirect
|
||||
github.com/uber-go/tally/v4 v4.1.7 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||
|
|
19
go.sum
19
go.sum
|
@ -191,8 +191,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
|
|||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
|
@ -254,10 +252,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
|
||||
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
|
||||
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
|
@ -267,10 +265,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
|||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
|
@ -412,8 +408,8 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
|
|||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
|
@ -564,11 +560,8 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
@ -1,33 +1,15 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func (h *BaseHandler) Error404(w http.ResponseWriter, r *http.Request) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"pages/404.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", &components.Base{
|
||||
func (h *BaseHandler) Error404(c echo.Context) error {
|
||||
return c.Render(http.StatusNotFound, "404.tmpl", &components.Base{
|
||||
NavbarActive: nil,
|
||||
Navbar: Pages,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("Error", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type ApiV1WorkersConnectGETResponse struct {
|
||||
|
@ -11,25 +15,38 @@ type ApiV1WorkersConnectGETResponse struct {
|
|||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
func (h *BaseHandler) ApiV1WorkersConnectGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
// Json response containing temporal endpoint
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
response := ApiV1WorkersConnectGETResponse{
|
||||
Endpoint: h.config.Temporal.ServerHost,
|
||||
Group: principal.Worker.Group,
|
||||
Slug: principal.Worker.Slug,
|
||||
Group: cc.Principal.Worker.Group,
|
||||
Slug: cc.Principal.Worker.Slug,
|
||||
}
|
||||
|
||||
responseJson, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = w.Write(responseJson)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// TODO: Can we instead get this from the Workflow outcome?
|
||||
//
|
||||
// To somehow listen for the outcomes and then store them automatically.
|
||||
func (h *BaseHandler) ApiV1HealthchecksHistoryPOST(c echo.Context) error {
|
||||
ctx := context.Background()
|
||||
|
||||
slug := c.Param("slug")
|
||||
|
||||
healthcheck, err := services.GetHealthcheckHttp(ctx, h.query, slug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = h.query.HealthcheckHttp.History.Model(healthcheck).Append(
|
||||
&models.HealthcheckHttpHistory{
|
||||
Status: "UP",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ package handlers
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jwtInternal "code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const sessionName = "zdravko-hey"
|
||||
|
@ -148,30 +148,33 @@ func (h *BaseHandler) ClearAuthenticatedUserForRequest(w http.ResponseWriter, r
|
|||
|
||||
type AuthenticatedHandler func(http.ResponseWriter, *http.Request, *AuthenticatedPrincipal)
|
||||
|
||||
func (h *BaseHandler) Authenticated(next AuthenticatedHandler) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type AuthenticatedContext struct {
|
||||
echo.Context
|
||||
Principal *AuthenticatedPrincipal
|
||||
}
|
||||
|
||||
func (h *BaseHandler) Authenticated(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// First try cookie authentication
|
||||
user, err := h.AuthenticateRequestWithCookies(r)
|
||||
user, err := h.AuthenticateRequestWithCookies(c.Request())
|
||||
if err == nil {
|
||||
if user.OAuth2Expiry.Before(time.Now()) {
|
||||
user, err = h.RefreshToken(w, r, user)
|
||||
user, err = h.RefreshToken(c.Response(), c.Request(), user)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/oauth2/login", http.StatusTemporaryRedirect)
|
||||
return
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/oauth2/login")
|
||||
}
|
||||
}
|
||||
next(w, r, &AuthenticatedPrincipal{user, nil})
|
||||
return
|
||||
|
||||
cc := AuthenticatedContext{c, &AuthenticatedPrincipal{user, nil}}
|
||||
return next(cc)
|
||||
}
|
||||
// Then try token based authentication
|
||||
principal, err := h.AuthenticateRequestWithToken(r)
|
||||
principal, err := h.AuthenticateRequestWithToken(c.Request())
|
||||
if err == nil {
|
||||
next(w, r, principal)
|
||||
return
|
||||
cc := AuthenticatedContext{c, principal}
|
||||
return next(cc)
|
||||
}
|
||||
|
||||
log.Println("err: ", err)
|
||||
|
||||
http.Redirect(w, r, "/oauth2/login", http.StatusTemporaryRedirect)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/oauth2/login")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@ package handlers
|
|||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type IndexData struct {
|
||||
|
@ -39,17 +38,8 @@ func newMockHealthCheck(domain string) *HealthCheck {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) Index(w http.ResponseWriter, r *http.Request) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"pages/index.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", &IndexData{
|
||||
func (h *BaseHandler) Index(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "index.tmpl", &IndexData{
|
||||
Base: &components.Base{
|
||||
NavbarActive: GetPageByTitle(Pages, "Status"),
|
||||
Navbar: Pages,
|
||||
|
@ -61,7 +51,4 @@ func (h *BaseHandler) Index(w http.ResponseWriter, r *http.Request) {
|
|||
newMockHealthCheck("foo.example.net"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
@ -79,64 +81,60 @@ func (h *BaseHandler) RefreshToken(w http.ResponseWriter, r *http.Request, user
|
|||
return refreshedUser, nil
|
||||
}
|
||||
|
||||
func (h *BaseHandler) OAuth2LoginGET(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *BaseHandler) OAuth2LoginGET(c echo.Context) error {
|
||||
conf := newOAuth2(h.config)
|
||||
|
||||
state := newRandomState()
|
||||
result := h.db.Create(&models.OAuth2State{State: state, Expiry: time.Now().Add(5 * time.Minute)})
|
||||
if result.Error != nil {
|
||||
http.Error(w, result.Error.Error(), http.StatusInternalServerError)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline)
|
||||
|
||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
}
|
||||
|
||||
func (h *BaseHandler) OAuth2CallbackGET(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *BaseHandler) OAuth2CallbackGET(c echo.Context) error {
|
||||
ctx := context.Background()
|
||||
conf := newOAuth2(h.config)
|
||||
|
||||
state := r.URL.Query().Get("state")
|
||||
state := c.QueryParam("state")
|
||||
code := c.QueryParam("code")
|
||||
|
||||
result, err := h.query.OAuth2State.WithContext(ctx).Where(
|
||||
h.query.OAuth2State.State.Eq(state),
|
||||
h.query.OAuth2State.Expiry.Gt(time.Now()),
|
||||
).Delete()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
http.Error(w, "Invalid state", http.StatusUnauthorized)
|
||||
return
|
||||
return errors.New("invalid state")
|
||||
}
|
||||
|
||||
// Exchange the code for a new token.
|
||||
tok, err := conf.Exchange(r.Context(), r.URL.Query().Get("code"))
|
||||
tok, err := conf.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
// Ge the user information.
|
||||
client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(tok))
|
||||
resp, err := client.Get(h.config.OAuth2.EndpointUserInfoURL)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
var userInfo UserInfo
|
||||
err = json.Unmarshal(body, &userInfo)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
userId := userInfo.Sub
|
||||
|
@ -144,7 +142,7 @@ func (h *BaseHandler) OAuth2CallbackGET(w http.ResponseWriter, r *http.Request)
|
|||
userId = strconv.Itoa(userInfo.Id)
|
||||
}
|
||||
|
||||
err = h.SetAuthenticatedUserForRequest(w, r, &AuthenticatedUser{
|
||||
err = h.SetAuthenticatedUserForRequest(c.Response(), c.Request(), &AuthenticatedUser{
|
||||
ID: userId,
|
||||
Email: userInfo.Email,
|
||||
OAuth2AccessToken: tok.AccessToken,
|
||||
|
@ -153,27 +151,28 @@ func (h *BaseHandler) OAuth2CallbackGET(w http.ResponseWriter, r *http.Request)
|
|||
OAuth2Expiry: tok.Expiry,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/settings", http.StatusTemporaryRedirect)
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/settings")
|
||||
}
|
||||
|
||||
func (h *BaseHandler) OAuth2LogoutGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
func (h *BaseHandler) OAuth2LogoutGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
if h.config.OAuth2.EndpointLogoutURL != "" {
|
||||
tok := h.AuthenticatedUserToOAuth2Token(principal.User)
|
||||
tok := h.AuthenticatedUserToOAuth2Token(cc.Principal.User)
|
||||
client := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(tok))
|
||||
_, err := client.Get(h.config.OAuth2.EndpointLogoutURL)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := h.ClearAuthenticatedUserForRequest(w, r)
|
||||
err := h.ClearAuthenticatedUserForRequest(c.Response(), c.Request())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
|
||||
return c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@ package handlers
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
|
@ -49,23 +48,12 @@ var SettingsNavbar = []*components.Page{
|
|||
GetPageByTitle(SettingsPages, "Logout"),
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsOverviewGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_overview.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", NewSettings(
|
||||
principal.User,
|
||||
return c.Render(http.StatusOK, "settings_overview.tmpl", NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Overview"),
|
||||
[]*components.Page{GetPageByTitle(SettingsPages, "Overview")},
|
||||
))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,13 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type SettingsHealthchecks struct {
|
||||
|
@ -27,58 +25,38 @@ type SettingsHealthcheck struct {
|
|||
Healthcheck *models.HealthcheckHttp
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsHealthchecksGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_healthchecks.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func (h *BaseHandler) SettingsHealthchecksGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
healthchecks, err := h.query.HealthcheckHttp.WithContext(context.Background()).Find()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", &SettingsHealthchecks{
|
||||
return c.Render(http.StatusOK, "settings_healthchecks.tmpl", &SettingsHealthchecks{
|
||||
Settings: NewSettings(
|
||||
principal.User,
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Healthchecks"),
|
||||
[]*components.Page{GetPageByTitle(SettingsPages, "Healthchecks")},
|
||||
),
|
||||
Healthchecks: healthchecks,
|
||||
HealthchecksLength: len(healthchecks),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsHealthchecksDescribeGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
func (h *BaseHandler) SettingsHealthchecksDescribeGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_healthchecks_describe.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
slug := c.Param("slug")
|
||||
|
||||
healthcheck, err := services.GetHealthcheckHttp(context.Background(), h.query, slug)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", &SettingsHealthcheck{
|
||||
return c.Render(http.StatusOK, "settings_healthchecks_describe.tmpl", &SettingsHealthcheck{
|
||||
Settings: NewSettings(
|
||||
principal.User,
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Healthchecks"),
|
||||
[]*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Healthchecks"),
|
||||
|
@ -90,52 +68,38 @@ func (h *BaseHandler) SettingsHealthchecksDescribeGET(w http.ResponseWriter, r *
|
|||
}),
|
||||
Healthcheck: healthcheck,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsHealthchecksCreateGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_healthchecks_create.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func (h *BaseHandler) SettingsHealthchecksCreateGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", NewSettings(
|
||||
principal.User,
|
||||
return c.Render(http.StatusOK, "settings_healthchecks_create.tmpl", NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Healthchecks"),
|
||||
[]*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Healthchecks"),
|
||||
GetPageByTitle(SettingsPages, "Healthchecks Create"),
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsHealthchecksCreatePOST(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
func (h *BaseHandler) SettingsHealthchecksCreatePOST(c echo.Context) error {
|
||||
ctx := context.Background()
|
||||
|
||||
healthcheckHttp := &models.HealthcheckHttp{
|
||||
Healthcheck: models.Healthcheck{
|
||||
Name: r.FormValue("name"),
|
||||
Slug: slug.Make(r.FormValue("name")),
|
||||
Schedule: r.FormValue("schedule"),
|
||||
WorkerGroups: strings.Split(r.FormValue("workergroups"), ","),
|
||||
Name: c.FormValue("name"),
|
||||
Slug: slug.Make(c.FormValue("name")),
|
||||
Schedule: c.FormValue("schedule"),
|
||||
WorkerGroups: strings.Split(c.FormValue("workergroups"), ","),
|
||||
},
|
||||
Url: r.FormValue("url"),
|
||||
Method: r.FormValue("method"),
|
||||
Url: c.FormValue("url"),
|
||||
Method: c.FormValue("method"),
|
||||
}
|
||||
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(healthcheckHttp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.CreateHealthcheckHttp(
|
||||
|
@ -144,13 +108,13 @@ func (h *BaseHandler) SettingsHealthchecksCreatePOST(w http.ResponseWriter, r *h
|
|||
healthcheckHttp,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.StartHealthcheckHttp(ctx, h.temporal, healthcheckHttp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/settings/healthchecks", http.StatusSeeOther)
|
||||
return c.Redirect(http.StatusSeeOther, "/settings/healthchecks")
|
||||
}
|
||||
|
|
|
@ -4,16 +4,14 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type SettingsWorkers struct {
|
||||
|
@ -27,58 +25,38 @@ type SettingsWorker struct {
|
|||
Worker *models.Worker
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsWorkersGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_workers.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func (h *BaseHandler) SettingsWorkersGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
workers, err := h.query.Worker.WithContext(context.Background()).Find()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", &SettingsWorkers{
|
||||
return c.Render(http.StatusOK, "settings_workers.tmpl", &SettingsWorkers{
|
||||
Settings: NewSettings(
|
||||
principal.User,
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Workers"),
|
||||
[]*components.Page{GetPageByTitle(SettingsPages, "Workers")},
|
||||
),
|
||||
Workers: workers,
|
||||
WorkersLength: len(workers),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsWorkersDescribeGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
func (h *BaseHandler) SettingsWorkersDescribeGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_workers_describe.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
slug := c.Param("slug")
|
||||
|
||||
worker, err := services.GetWorker(context.Background(), h.query, slug)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", &SettingsWorker{
|
||||
return c.Render(http.StatusOK, "setting_workers_describe.tmpl", &SettingsWorker{
|
||||
Settings: NewSettings(
|
||||
principal.User,
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Workers"),
|
||||
[]*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Workers"),
|
||||
|
@ -90,47 +68,33 @@ func (h *BaseHandler) SettingsWorkersDescribeGET(w http.ResponseWriter, r *http.
|
|||
}),
|
||||
Worker: worker,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsWorkersCreateGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
ts, err := template.ParseFS(templates.Templates,
|
||||
"components/base.tmpl",
|
||||
"components/settings.tmpl",
|
||||
"pages/settings_workers_create.tmpl",
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func (h *BaseHandler) SettingsWorkersCreateGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
err = ts.ExecuteTemplate(w, "base", NewSettings(
|
||||
principal.User,
|
||||
return c.Render(http.StatusOK, "settings_workers_create.tmpl", NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Workers"),
|
||||
[]*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Workers"),
|
||||
GetPageByTitle(SettingsPages, "Workers Create"),
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsWorkersCreatePOST(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
func (h *BaseHandler) SettingsWorkersCreatePOST(c echo.Context) error {
|
||||
ctx := context.Background()
|
||||
|
||||
worker := &models.Worker{
|
||||
Name: r.FormValue("name"),
|
||||
Slug: slug.Make(r.FormValue("name")),
|
||||
Group: r.FormValue("group"),
|
||||
Name: c.FormValue("name"),
|
||||
Slug: slug.Make(c.FormValue("name")),
|
||||
Group: c.FormValue("group"),
|
||||
}
|
||||
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(worker)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.CreateWorker(
|
||||
|
@ -139,27 +103,25 @@ func (h *BaseHandler) SettingsWorkersCreatePOST(w http.ResponseWriter, r *http.R
|
|||
worker,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/settings/workers", http.StatusSeeOther)
|
||||
return c.Redirect(http.StatusSeeOther, "/settings/workers")
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsWorkersTokenGET(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
func (h *BaseHandler) SettingsWorkersTokenGET(c echo.Context) error {
|
||||
slug := c.Param("slug")
|
||||
|
||||
worker, err := services.GetWorker(context.Background(), h.query, slug)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
// Allow write access to default namespace
|
||||
token, err := jwt.NewTokenForWorker(h.config.Jwt.PrivateKey, h.config.Jwt.PublicKey, worker)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"token": "` + token + `"}`))
|
||||
return c.JSON(http.StatusOK, map[string]string{"token": token})
|
||||
}
|
||||
|
|
|
@ -6,9 +6,12 @@ import (
|
|||
"net/url"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func (h *BaseHandler) Temporal(w http.ResponseWriter, r *http.Request, principal *AuthenticatedPrincipal) {
|
||||
func (h *BaseHandler) Temporal(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
||||
Host: h.config.Temporal.UIHost,
|
||||
Scheme: "http",
|
||||
|
@ -23,10 +26,11 @@ func (h *BaseHandler) Temporal(w http.ResponseWriter, r *http.Request, principal
|
|||
token, _ := jwt.NewTokenForUser(
|
||||
h.config.Jwt.PrivateKey,
|
||||
h.config.Jwt.PublicKey,
|
||||
principal.User.Email,
|
||||
cc.Principal.User.Email,
|
||||
)
|
||||
r.Header.Add("Authorization", "Bearer "+token)
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(w, r)
|
||||
proxy.ServeHTTP(c.Response(), c.Request())
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ type HealthcheckHttp struct {
|
|||
Healthcheck
|
||||
Url string `validate:"required,url"`
|
||||
Method string `validate:"required,oneof=GET POST"`
|
||||
|
||||
History []HealthcheckHttpHistory `gorm:"foreignKey:ID"`
|
||||
}
|
||||
|
||||
type HealthcheckTcp struct {
|
||||
|
|
|
@ -36,6 +36,19 @@ func newHealthcheckHttpHistory(db *gorm.DB, opts ...gen.DOOption) healthcheckHtt
|
|||
db: db.Session(&gorm.Session{}),
|
||||
|
||||
RelationField: field.NewRelation("HealthcheckHTTP", "models.HealthcheckHttp"),
|
||||
History: struct {
|
||||
field.RelationField
|
||||
HealthcheckHTTP struct {
|
||||
field.RelationField
|
||||
}
|
||||
}{
|
||||
RelationField: field.NewRelation("HealthcheckHTTP.History", "models.HealthcheckHttpHistory"),
|
||||
HealthcheckHTTP: struct {
|
||||
field.RelationField
|
||||
}{
|
||||
RelationField: field.NewRelation("HealthcheckHTTP.History.HealthcheckHTTP", "models.HealthcheckHttp"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_healthcheckHttpHistory.fillFieldMap()
|
||||
|
@ -125,6 +138,13 @@ type healthcheckHttpHistoryHasOneHealthcheckHTTP struct {
|
|||
db *gorm.DB
|
||||
|
||||
field.RelationField
|
||||
|
||||
History struct {
|
||||
field.RelationField
|
||||
HealthcheckHTTP struct {
|
||||
field.RelationField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHistoryHasOneHealthcheckHTTP) Where(conds ...field.Expr) *healthcheckHttpHistoryHasOneHealthcheckHTTP {
|
||||
|
|
|
@ -36,9 +36,27 @@ func newHealthcheckHttp(db *gorm.DB, opts ...gen.DOOption) healthcheckHttp {
|
|||
_healthcheckHttp.Status = field.NewString(tableName, "status")
|
||||
_healthcheckHttp.UptimePercentage = field.NewFloat64(tableName, "uptime_percentage")
|
||||
_healthcheckHttp.Schedule = field.NewString(tableName, "schedule")
|
||||
_healthcheckHttp.Groups = field.NewField(tableName, "groups")
|
||||
_healthcheckHttp.WorkerGroups = field.NewField(tableName, "worker_groups")
|
||||
_healthcheckHttp.Url = field.NewString(tableName, "url")
|
||||
_healthcheckHttp.Method = field.NewString(tableName, "method")
|
||||
_healthcheckHttp.History = healthcheckHttpHasManyHistory{
|
||||
db: db.Session(&gorm.Session{}),
|
||||
|
||||
RelationField: field.NewRelation("History", "models.HealthcheckHttpHistory"),
|
||||
HealthcheckHTTP: struct {
|
||||
field.RelationField
|
||||
History struct {
|
||||
field.RelationField
|
||||
}
|
||||
}{
|
||||
RelationField: field.NewRelation("History.HealthcheckHTTP", "models.HealthcheckHttp"),
|
||||
History: struct {
|
||||
field.RelationField
|
||||
}{
|
||||
RelationField: field.NewRelation("History.HealthcheckHTTP.History", "models.HealthcheckHttpHistory"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_healthcheckHttp.fillFieldMap()
|
||||
|
||||
|
@ -58,9 +76,10 @@ type healthcheckHttp struct {
|
|||
Status field.String
|
||||
UptimePercentage field.Float64
|
||||
Schedule field.String
|
||||
Groups field.Field
|
||||
WorkerGroups field.Field
|
||||
Url field.String
|
||||
Method field.String
|
||||
History healthcheckHttpHasManyHistory
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
|
@ -86,7 +105,7 @@ func (h *healthcheckHttp) updateTableName(table string) *healthcheckHttp {
|
|||
h.Status = field.NewString(table, "status")
|
||||
h.UptimePercentage = field.NewFloat64(table, "uptime_percentage")
|
||||
h.Schedule = field.NewString(table, "schedule")
|
||||
h.Groups = field.NewField(table, "groups")
|
||||
h.WorkerGroups = field.NewField(table, "worker_groups")
|
||||
h.Url = field.NewString(table, "url")
|
||||
h.Method = field.NewString(table, "method")
|
||||
|
||||
|
@ -117,7 +136,7 @@ func (h *healthcheckHttp) GetFieldByName(fieldName string) (field.OrderExpr, boo
|
|||
}
|
||||
|
||||
func (h *healthcheckHttp) fillFieldMap() {
|
||||
h.fieldMap = make(map[string]field.Expr, 12)
|
||||
h.fieldMap = make(map[string]field.Expr, 13)
|
||||
h.fieldMap["id"] = h.ID
|
||||
h.fieldMap["created_at"] = h.CreatedAt
|
||||
h.fieldMap["updated_at"] = h.UpdatedAt
|
||||
|
@ -127,9 +146,10 @@ func (h *healthcheckHttp) fillFieldMap() {
|
|||
h.fieldMap["status"] = h.Status
|
||||
h.fieldMap["uptime_percentage"] = h.UptimePercentage
|
||||
h.fieldMap["schedule"] = h.Schedule
|
||||
h.fieldMap["groups"] = h.Groups
|
||||
h.fieldMap["worker_groups"] = h.WorkerGroups
|
||||
h.fieldMap["url"] = h.Url
|
||||
h.fieldMap["method"] = h.Method
|
||||
|
||||
}
|
||||
|
||||
func (h healthcheckHttp) clone(db *gorm.DB) healthcheckHttp {
|
||||
|
@ -142,6 +162,84 @@ func (h healthcheckHttp) replaceDB(db *gorm.DB) healthcheckHttp {
|
|||
return h
|
||||
}
|
||||
|
||||
type healthcheckHttpHasManyHistory struct {
|
||||
db *gorm.DB
|
||||
|
||||
field.RelationField
|
||||
|
||||
HealthcheckHTTP struct {
|
||||
field.RelationField
|
||||
History struct {
|
||||
field.RelationField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistory) Where(conds ...field.Expr) *healthcheckHttpHasManyHistory {
|
||||
if len(conds) == 0 {
|
||||
return &a
|
||||
}
|
||||
|
||||
exprs := make([]clause.Expression, 0, len(conds))
|
||||
for _, cond := range conds {
|
||||
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||
}
|
||||
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistory) WithContext(ctx context.Context) *healthcheckHttpHasManyHistory {
|
||||
a.db = a.db.WithContext(ctx)
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistory) Session(session *gorm.Session) *healthcheckHttpHasManyHistory {
|
||||
a.db = a.db.Session(session)
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistory) Model(m *models.HealthcheckHttp) *healthcheckHttpHasManyHistoryTx {
|
||||
return &healthcheckHttpHasManyHistoryTx{a.db.Model(m).Association(a.Name())}
|
||||
}
|
||||
|
||||
type healthcheckHttpHasManyHistoryTx struct{ tx *gorm.Association }
|
||||
|
||||
func (a healthcheckHttpHasManyHistoryTx) Find() (result []*models.HealthcheckHttpHistory, err error) {
|
||||
return result, a.tx.Find(&result)
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistoryTx) Append(values ...*models.HealthcheckHttpHistory) (err error) {
|
||||
targetValues := make([]interface{}, len(values))
|
||||
for i, v := range values {
|
||||
targetValues[i] = v
|
||||
}
|
||||
return a.tx.Append(targetValues...)
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistoryTx) Replace(values ...*models.HealthcheckHttpHistory) (err error) {
|
||||
targetValues := make([]interface{}, len(values))
|
||||
for i, v := range values {
|
||||
targetValues[i] = v
|
||||
}
|
||||
return a.tx.Replace(targetValues...)
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistoryTx) Delete(values ...*models.HealthcheckHttpHistory) (err error) {
|
||||
targetValues := make([]interface{}, len(values))
|
||||
for i, v := range values {
|
||||
targetValues[i] = v
|
||||
}
|
||||
return a.tx.Delete(targetValues...)
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistoryTx) Clear() error {
|
||||
return a.tx.Clear()
|
||||
}
|
||||
|
||||
func (a healthcheckHttpHasManyHistoryTx) Count() int64 {
|
||||
return a.tx.Count()
|
||||
}
|
||||
|
||||
type healthcheckHttpDo struct{ gen.DO }
|
||||
|
||||
type IHealthcheckHttpDo interface {
|
||||
|
|
|
@ -36,7 +36,7 @@ func newHealthcheckTcp(db *gorm.DB, opts ...gen.DOOption) healthcheckTcp {
|
|||
_healthcheckTcp.Status = field.NewString(tableName, "status")
|
||||
_healthcheckTcp.UptimePercentage = field.NewFloat64(tableName, "uptime_percentage")
|
||||
_healthcheckTcp.Schedule = field.NewString(tableName, "schedule")
|
||||
_healthcheckTcp.Groups = field.NewField(tableName, "groups")
|
||||
_healthcheckTcp.WorkerGroups = field.NewField(tableName, "worker_groups")
|
||||
_healthcheckTcp.Hostname = field.NewString(tableName, "hostname")
|
||||
_healthcheckTcp.Port = field.NewInt(tableName, "port")
|
||||
|
||||
|
@ -58,7 +58,7 @@ type healthcheckTcp struct {
|
|||
Status field.String
|
||||
UptimePercentage field.Float64
|
||||
Schedule field.String
|
||||
Groups field.Field
|
||||
WorkerGroups field.Field
|
||||
Hostname field.String
|
||||
Port field.Int
|
||||
|
||||
|
@ -86,7 +86,7 @@ func (h *healthcheckTcp) updateTableName(table string) *healthcheckTcp {
|
|||
h.Status = field.NewString(table, "status")
|
||||
h.UptimePercentage = field.NewFloat64(table, "uptime_percentage")
|
||||
h.Schedule = field.NewString(table, "schedule")
|
||||
h.Groups = field.NewField(table, "groups")
|
||||
h.WorkerGroups = field.NewField(table, "worker_groups")
|
||||
h.Hostname = field.NewString(table, "hostname")
|
||||
h.Port = field.NewInt(table, "port")
|
||||
|
||||
|
@ -127,7 +127,7 @@ func (h *healthcheckTcp) fillFieldMap() {
|
|||
h.fieldMap["status"] = h.Status
|
||||
h.fieldMap["uptime_percentage"] = h.UptimePercentage
|
||||
h.fieldMap["schedule"] = h.Schedule
|
||||
h.fieldMap["groups"] = h.Groups
|
||||
h.fieldMap["worker_groups"] = h.WorkerGroups
|
||||
h.fieldMap["hostname"] = h.Hostname
|
||||
h.fieldMap["port"] = h.Port
|
||||
}
|
||||
|
|
|
@ -29,14 +29,16 @@ func StartHealthcheckHttp(ctx context.Context, t client.Client, healthcheckHttp
|
|||
args := make([]interface{}, 0)
|
||||
args = append(args, workflows.HealthcheckHttpWorkflowParam{Url: healthcheckHttp.Url, Method: healthcheckHttp.Method})
|
||||
|
||||
id := "healthcheck-http-" + healthcheckHttp.Slug
|
||||
|
||||
for _, group := range healthcheckHttp.WorkerGroups {
|
||||
_, err := t.ScheduleClient().Create(ctx, client.ScheduleOptions{
|
||||
ID: "healthcheck-http-" + healthcheckHttp.Slug,
|
||||
ID: id + "-" + group,
|
||||
Spec: client.ScheduleSpec{
|
||||
CronExpressions: []string{healthcheckHttp.Schedule},
|
||||
},
|
||||
Action: &client.ScheduleWorkflowAction{
|
||||
ID: "healthcheck-http-" + healthcheckHttp.Slug,
|
||||
ID: id + "-" + group,
|
||||
Workflow: workflows.HealthcheckHttpWorkflowDefinition,
|
||||
Args: args,
|
||||
TaskQueue: group,
|
||||
|
@ -52,3 +54,7 @@ func StartHealthcheckHttp(ctx context.Context, t client.Client, healthcheckHttp
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateHealthcheckHistory(ctx context.Context, db *gorm.DB, healthcheckHistory *models.HealthcheckHttpHistory) error {
|
||||
return db.WithContext(ctx).Create(healthcheckHistory).Error
|
||||
}
|
||||
|
|
7
justfile
7
justfile
|
@ -26,7 +26,12 @@ run-worker:
|
|||
# Start server
|
||||
run-server:
|
||||
go build -o dist/zdravko cmd/zdravko/main.go
|
||||
./dist/zdravko --server --temporal
|
||||
./dist/zdravko --server
|
||||
|
||||
# Start temporal
|
||||
run-temporal:
|
||||
go build -o dist/zdravko cmd/zdravko/main.go
|
||||
./dist/zdravko --temporal
|
||||
|
||||
# Generates new jwt key pair
|
||||
generate-jwt-key:
|
||||
|
|
|
@ -10,7 +10,9 @@ import (
|
|||
"code.tjo.space/mentos1386/zdravko/internal/handlers"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/temporal"
|
||||
"code.tjo.space/mentos1386/zdravko/web/static"
|
||||
"github.com/gorilla/mux"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -29,7 +31,10 @@ func (s *Server) Name() string {
|
|||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
r := mux.NewRouter()
|
||||
e := echo.New()
|
||||
e.Renderer = templates.NewTemplates()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
db, query, err := internal.ConnectToDatabase(s.cfg.DatabasePath)
|
||||
if err != nil {
|
||||
|
@ -46,56 +51,70 @@ func (s *Server) Start() error {
|
|||
h := handlers.NewBaseHandler(db, query, temporalClient, s.cfg)
|
||||
|
||||
// Health
|
||||
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
e.GET("/health", func(c echo.Context) error {
|
||||
d, err := db.DB()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
err = d.Ping()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
_, err = w.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||
})
|
||||
|
||||
// Server static files
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static.Static))))
|
||||
stat := e.Group("/static")
|
||||
stat.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||
Filesystem: http.FS(static.Static),
|
||||
}))
|
||||
|
||||
r.HandleFunc("/", h.Index).Methods("GET")
|
||||
// Public
|
||||
e.GET("", h.Index)
|
||||
|
||||
// Settings
|
||||
r.HandleFunc("/settings", h.Authenticated(h.SettingsOverviewGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/healthchecks", h.Authenticated(h.SettingsHealthchecksGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/healthchecks/create", h.Authenticated(h.SettingsHealthchecksCreateGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/healthchecks/create", h.Authenticated(h.SettingsHealthchecksCreatePOST)).Methods("POST")
|
||||
r.HandleFunc("/settings/healthchecks/{slug}", h.Authenticated(h.SettingsHealthchecksDescribeGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/workers", h.Authenticated(h.SettingsWorkersGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/workers/create", h.Authenticated(h.SettingsWorkersCreateGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/workers/create", h.Authenticated(h.SettingsWorkersCreatePOST)).Methods("POST")
|
||||
r.HandleFunc("/settings/workers/{slug}", h.Authenticated(h.SettingsWorkersDescribeGET)).Methods("GET")
|
||||
r.HandleFunc("/settings/workers/{slug}/token", h.Authenticated(h.SettingsWorkersTokenGET)).Methods("GET")
|
||||
r.PathPrefix("/settings/temporal").HandlerFunc(h.Authenticated(h.Temporal))
|
||||
settings := e.Group("/settings")
|
||||
settings.Use(h.Authenticated)
|
||||
settings.GET("", h.SettingsOverviewGET)
|
||||
settings.GET("/healthchecks", h.SettingsHealthchecksGET)
|
||||
settings.GET("/healthchecks/create", h.SettingsHealthchecksCreateGET)
|
||||
settings.POST("/healthchecks/create", h.SettingsHealthchecksCreatePOST)
|
||||
settings.GET("/healthchecks/:slug", h.SettingsHealthchecksDescribeGET)
|
||||
settings.GET("/workers", h.SettingsWorkersGET)
|
||||
settings.GET("/workers/create", h.SettingsWorkersCreateGET)
|
||||
settings.POST("/workers/create", h.SettingsWorkersCreatePOST)
|
||||
settings.GET("/workers/:slug", h.SettingsWorkersDescribeGET)
|
||||
settings.GET("/workers/:slug/token", h.SettingsWorkersTokenGET)
|
||||
settings.Match([]string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"}, "/temporal*", h.Temporal)
|
||||
|
||||
// OAuth2
|
||||
r.HandleFunc("/oauth2/login", h.OAuth2LoginGET).Methods("GET")
|
||||
r.HandleFunc("/oauth2/callback", h.OAuth2CallbackGET).Methods("GET")
|
||||
r.HandleFunc("/oauth2/logout", h.Authenticated(h.OAuth2LogoutGET)).Methods("GET")
|
||||
oauth2 := e.Group("/oauth2")
|
||||
oauth2.GET("/login", h.OAuth2LoginGET)
|
||||
oauth2.GET("/callback", h.OAuth2CallbackGET)
|
||||
oauth2.GET("/logout", h.OAuth2LogoutGET, h.Authenticated)
|
||||
|
||||
// API
|
||||
r.HandleFunc("/api/v1/workers/connect", h.Authenticated(h.ApiV1WorkersConnectGET)).Methods("GET")
|
||||
apiv1 := e.Group("/api/v1")
|
||||
apiv1.Use(h.Authenticated)
|
||||
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
|
||||
apiv1.POST("/healthcheck/:slug/history", h.ApiV1HealthchecksHistoryPOST)
|
||||
|
||||
// 404
|
||||
r.PathPrefix("/").HandlerFunc(h.Error404).Methods("GET")
|
||||
// Error handler
|
||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
code := http.StatusInternalServerError
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
code = he.Code
|
||||
}
|
||||
|
||||
s.server = &http.Server{
|
||||
Addr: ":" + s.cfg.Port,
|
||||
Handler: r,
|
||||
if code == http.StatusNotFound {
|
||||
_ = h.Error404(c)
|
||||
return
|
||||
}
|
||||
_ = c.String(code, err.Error())
|
||||
}
|
||||
|
||||
return s.server.ListenAndServe()
|
||||
return e.Start(":" + s.cfg.Port)
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
|
|
|
@ -2,7 +2,57 @@ package templates
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
"log"
|
||||
"text/template"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
//go:embed *
|
||||
var Templates embed.FS
|
||||
var templates embed.FS
|
||||
|
||||
const base = "components/base.tmpl"
|
||||
|
||||
type Templates struct {
|
||||
templates map[string]*template.Template
|
||||
}
|
||||
|
||||
func load(files ...string) *template.Template {
|
||||
files = append(files, base)
|
||||
return template.Must(template.ParseFS(templates, files...))
|
||||
}
|
||||
|
||||
func loadSettings(files ...string) *template.Template {
|
||||
files = append(files, "components/settings.tmpl")
|
||||
return load(files...)
|
||||
}
|
||||
|
||||
func NewTemplates() *Templates {
|
||||
return &Templates{
|
||||
templates: map[string]*template.Template{
|
||||
"404.tmpl": load("pages/404.tmpl"),
|
||||
"index.tmpl": load("pages/index.tmpl"),
|
||||
"settings_overview.tmpl": loadSettings("pages/settings_overview.tmpl"),
|
||||
"settings_workers.tmpl": loadSettings("pages/settings_workers.tmpl"),
|
||||
"settings_workers_create.tmpl": loadSettings("pages/settings_workers_create.tmpl"),
|
||||
"settings_healthchecks.tmpl": loadSettings("pages/settings_healthchecks.tmpl"),
|
||||
"settings_healthchecks_create.tmpl": loadSettings("pages/settings_healthchecks_create.tmpl"),
|
||||
"settings_healthchecks_describe.tmpl": loadSettings("pages/settings_healthchecks_describe.tmpl"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Templates) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
if t.templates[name] == nil {
|
||||
log.Printf("template not found: %s", name)
|
||||
return echo.ErrNotFound
|
||||
}
|
||||
|
||||
err := t.templates[name].ExecuteTemplate(w, "base", data)
|
||||
if err != nil {
|
||||
log.Printf("error rendering template: %s", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue