feat: move sentry configuration from frontend to api
This commit is contained in:
@ -114,39 +114,7 @@ func NewEcho() *echo.Echo {
|
||||
// panic recover
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
if config.ServiceSentryDsn.GetString() != "" {
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: config.ServiceSentryDsn.GetString(),
|
||||
AttachStacktrace: true,
|
||||
Release: version.Version,
|
||||
}); err != nil {
|
||||
log.Criticalf("Sentry init failed: %s", err)
|
||||
}
|
||||
defer sentry.Flush(5 * time.Second)
|
||||
|
||||
e.Use(sentryecho.New(sentryecho.Options{
|
||||
Repanic: true,
|
||||
}))
|
||||
|
||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
// Only capture errors not already handled by echo
|
||||
var herr *echo.HTTPError
|
||||
if errors.As(err, &herr) && herr.Code > 403 {
|
||||
hub := sentryecho.GetHubFromContext(c)
|
||||
if hub != nil {
|
||||
hub.WithScope(func(scope *sentry.Scope) {
|
||||
scope.SetExtra("url", c.Request().URL)
|
||||
hub.CaptureException(err)
|
||||
})
|
||||
} else {
|
||||
sentry.CaptureException(err)
|
||||
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
|
||||
}
|
||||
log.Debugf("Error '%s' sent to sentry", err.Error())
|
||||
}
|
||||
e.DefaultHTTPErrorHandler(err, c)
|
||||
}
|
||||
}
|
||||
setupSentry(e)
|
||||
|
||||
// Validation
|
||||
e.Validator = &CustomValidator{}
|
||||
@ -162,6 +130,44 @@ func NewEcho() *echo.Echo {
|
||||
return e
|
||||
}
|
||||
|
||||
func setupSentry(e *echo.Echo) {
|
||||
if !config.SentryEnabled.GetBool() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: config.SentryDsn.GetString(),
|
||||
AttachStacktrace: true,
|
||||
Release: version.Version,
|
||||
}); err != nil {
|
||||
log.Criticalf("Sentry init failed: %s", err)
|
||||
}
|
||||
defer sentry.Flush(5 * time.Second)
|
||||
|
||||
e.Use(sentryecho.New(sentryecho.Options{
|
||||
Repanic: true,
|
||||
}))
|
||||
|
||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
// Only capture errors not already handled by echo
|
||||
var herr *echo.HTTPError
|
||||
if errors.As(err, &herr) && herr.Code > 403 {
|
||||
hub := sentryecho.GetHubFromContext(c)
|
||||
if hub != nil {
|
||||
hub.WithScope(func(scope *sentry.Scope) {
|
||||
scope.SetExtra("url", c.Request().URL)
|
||||
hub.CaptureException(err)
|
||||
})
|
||||
} else {
|
||||
sentry.CaptureException(err)
|
||||
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
|
||||
}
|
||||
log.Debugf("Error '%s' sent to sentry", err.Error())
|
||||
}
|
||||
e.DefaultHTTPErrorHandler(err, c)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all routes for the application
|
||||
func RegisterRoutes(e *echo.Echo) {
|
||||
|
||||
|
@ -2,6 +2,7 @@ package routes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"code.vikunja.io/api/frontend"
|
||||
|
||||
@ -22,20 +24,88 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
indexFile = `index.html`
|
||||
rootPath = `dist/`
|
||||
cacheControlMax = `max-age=315360000, public, max-age=31536000, s-maxage=31536000, immutable`
|
||||
cacheControlNone = `public, max-age=0, s-maxage=0, must-revalidate`
|
||||
indexFile = `index.html`
|
||||
rootPath = `dist/`
|
||||
cacheControlMax = `max-age=315360000, public, max-age=31536000, s-maxage=31536000, immutable`
|
||||
cacheControlNone = `public, max-age=0, s-maxage=0, must-revalidate`
|
||||
configScriptTagTemplate = `
|
||||
<script>
|
||||
window.SENTRY_ENABLED = {{ .SENTRY_ENABLED }}
|
||||
window.SENTRY_DSN = '{{ .SENTRY_DSN }}'
|
||||
window.ALLOW_ICON_CHANGES = {{ .ALLOW_ICON_CHANGES }}
|
||||
window.CUSTOM_LOGO_URL = '{{ .CUSTOM_LOGO_URL }}'
|
||||
</script>`
|
||||
)
|
||||
|
||||
// Because the files are embedded into the final binary, we can be absolutely sure the etag will never change
|
||||
// and we can cache its generation pretty heavily.
|
||||
var etagCache map[string]string
|
||||
var etagLock sync.Mutex
|
||||
var scriptConfigString string
|
||||
var scriptConfigStringLock sync.Mutex
|
||||
|
||||
func init() {
|
||||
etagCache = make(map[string]string)
|
||||
etagLock = sync.Mutex{}
|
||||
scriptConfigStringLock = sync.Mutex{}
|
||||
}
|
||||
|
||||
func serveIndexFile(c echo.Context, assetFs http.FileSystem) (err error) {
|
||||
index, err := assetFs.Open(path.Join(rootPath, indexFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer index.Close()
|
||||
|
||||
if scriptConfigString == "" {
|
||||
|
||||
scriptConfigStringLock.Lock()
|
||||
defer scriptConfigStringLock.Unlock()
|
||||
|
||||
// replace config variables
|
||||
tmpl, err := template.New("config").Parse(configScriptTagTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tplOutput bytes.Buffer
|
||||
data := make(map[string]string)
|
||||
|
||||
data["SENTRY_ENABLED"] = "false"
|
||||
if config.SentryFrontendEnabled.GetBool() {
|
||||
data["SENTRY_ENABLED"] = "true"
|
||||
}
|
||||
data["SENTRY_DSN"] = config.SentryFrontendDsn.GetString()
|
||||
data["ALLOW_ICON_CHANGES"] = "true" // TODO
|
||||
data["CUSTOM_LOGO_URL"] = "" // TODO
|
||||
|
||||
err = tmpl.Execute(&tplOutput, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scriptConfig := tplOutput.String()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
_, err = buf.ReadFrom(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scriptConfigString = strings.ReplaceAll(buf.String(), `<div id="app"></div>`, `<div id="app"></div>`+scriptConfig)
|
||||
}
|
||||
|
||||
reader := strings.NewReader(scriptConfigString)
|
||||
|
||||
info, err := index.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
etag, err := generateEtag(index, info.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return serveFile(c, reader, info, etag)
|
||||
}
|
||||
|
||||
// Copied from echo's middleware.StaticWithConfig simplified and adjusted for caching
|
||||
@ -71,10 +141,8 @@ func static() echo.MiddlewareFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err = assetFs.Open(path.Join(rootPath, indexFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle all other requests with the index file
|
||||
return serveIndexFile(c, assetFs)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
@ -85,24 +153,7 @@ func static() echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
index, err := assetFs.Open(path.Join(name, indexFile))
|
||||
if err != nil {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
defer index.Close()
|
||||
|
||||
info, err = index.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
etag, err := generateEtag(index, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return serveFile(c, index, info, etag)
|
||||
return serveIndexFile(c, assetFs)
|
||||
}
|
||||
|
||||
etag, err := generateEtag(file, name)
|
||||
@ -133,7 +184,7 @@ func generateEtag(file http.File, name string) (etag string, err error) {
|
||||
}
|
||||
|
||||
// copied from http.serveContent
|
||||
func getMimeType(name string, file http.File) (mineType string, err error) {
|
||||
func getMimeType(name string, file io.ReadSeeker) (mineType string, err error) {
|
||||
mineType = mime.TypeByExtension(filepath.Ext(name))
|
||||
if mineType == "" {
|
||||
// read a chunk to decide between utf-8 text and binary
|
||||
@ -149,7 +200,7 @@ func getMimeType(name string, file http.File) (mineType string, err error) {
|
||||
return mineType, nil
|
||||
}
|
||||
|
||||
func getCacheControlHeader(info os.FileInfo, file http.File) (header string, err error) {
|
||||
func getCacheControlHeader(info os.FileInfo, file io.ReadSeeker) (header string, err error) {
|
||||
// Don't cache service worker and related files
|
||||
if info.Name() == "robots.txt" ||
|
||||
info.Name() == "sw.js" ||
|
||||
@ -187,7 +238,7 @@ func getCacheControlHeader(info os.FileInfo, file http.File) (header string, err
|
||||
return cacheControlNone, nil
|
||||
}
|
||||
|
||||
func serveFile(c echo.Context, file http.File, info os.FileInfo, etag string) error {
|
||||
func serveFile(c echo.Context, file io.ReadSeeker, info os.FileInfo, etag string) error {
|
||||
|
||||
c.Response().Header().Set("Server", "Vikunja")
|
||||
c.Response().Header().Set("Vary", "Accept-Encoding")
|
||||
|
Reference in New Issue
Block a user