942eb7c3d8
This is a major breaking change that moves away from having the entire app configuration in external TOML files to settings being in the database with a UI to update them dynamically. The app loads all config into memory (app settings, SMTP conf) on boot. "Hot" replacing them is complex and it's a fair tradeoff to instead just restart the application as it is practically instant. A new `settings` table stores arbitrary string keys with a JSONB value field which happens to support arbitrary types. After every settings update, the app gracefully releases all resources (HTTP server, DB pool, SMTP pool etc.) and restarts itself, occupying the same PID. If there are any running campaigns, the auto-restart doesn't happen and the user is prompted to invoke it manually with a one-click button once all running campaigns have been paused.
87 lines
2.1 KiB
Go
87 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/jmoiron/sqlx/types"
|
|
"github.com/labstack/echo"
|
|
)
|
|
|
|
type configScript struct {
|
|
RootURL string `json:"rootURL"`
|
|
FromEmail string `json:"fromEmail"`
|
|
Messengers []string `json:"messengers"`
|
|
MediaProvider string `json:"mediaProvider"`
|
|
NeedsRestart bool `json:"needsRestart"`
|
|
}
|
|
|
|
// handleGetConfigScript returns general configuration as a Javascript
|
|
// variable that can be included in an HTML page directly.
|
|
func handleGetConfigScript(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
out = configScript{
|
|
RootURL: app.constants.RootURL,
|
|
FromEmail: app.constants.FromEmail,
|
|
Messengers: app.manager.GetMessengerNames(),
|
|
MediaProvider: app.constants.MediaProvider,
|
|
}
|
|
)
|
|
|
|
app.Lock()
|
|
out.NeedsRestart = app.needsRestart
|
|
app.Unlock()
|
|
|
|
var (
|
|
b = bytes.Buffer{}
|
|
j = json.NewEncoder(&b)
|
|
)
|
|
b.Write([]byte(`var CONFIG = `))
|
|
_ = j.Encode(out)
|
|
return c.Blob(http.StatusOK, "application/javascript", b.Bytes())
|
|
}
|
|
|
|
// handleGetDashboardCharts returns chart data points to render ont he dashboard.
|
|
func handleGetDashboardCharts(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
out types.JSONText
|
|
)
|
|
|
|
if err := app.queries.GetDashboardCharts.Get(&out); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
|
fmt.Sprintf("Error fetching dashboard stats: %s", pqErrMsg(err)))
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, okResp{out})
|
|
}
|
|
|
|
// handleGetDashboardCounts returns stats counts to show on the dashboard.
|
|
func handleGetDashboardCounts(c echo.Context) error {
|
|
var (
|
|
app = c.Get("app").(*App)
|
|
out types.JSONText
|
|
)
|
|
|
|
if err := app.queries.GetDashboardCounts.Get(&out); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
|
fmt.Sprintf("Error fetching dashboard statsc counts: %s", pqErrMsg(err)))
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, okResp{out})
|
|
}
|
|
|
|
// handleReloadApp restarts the app.
|
|
func handleReloadApp(c echo.Context) error {
|
|
app := c.Get("app").(*App)
|
|
go func() {
|
|
<-time.After(time.Millisecond * 500)
|
|
app.sigChan <- syscall.SIGHUP
|
|
}()
|
|
return c.JSON(http.StatusOK, okResp{true})
|
|
}
|